Generalize merging of similar types during building

Before we had to special-case every type in the type
builder, now types just give their "identity", which is what
they want to be merged by, and the type builder only
needs one single map to loop up.
This commit is contained in:
Mark Probst 2018-04-15 11:07:56 -07:00
Родитель 48b63c5893
Коммит 1de5ba10ec
4 изменённых файлов: 225 добавлений и 178 удалений

Просмотреть файл

@ -73,14 +73,6 @@ export function mapOptional<T, U>(f: (x: T) => U, x: T | undefined): U | undefin
return f(x);
}
export function ifUndefined<T>(x: any, f: () => T): T | undefined {
if (x === undefined) {
return f();
} else {
return undefined;
}
}
export function defined<T>(x: T | undefined): T {
if (x !== undefined) return x;
return panic("Defined value expected, but got undefined");

Просмотреть файл

@ -1,8 +1,8 @@
"use strict";
import { OrderedSet, OrderedMap, Set, is, hash } from "immutable";
import { OrderedSet, OrderedMap, Set, is, hash, List } from "immutable";
import { defined, panic, assert, mapOptional } from "./Support";
import { defined, panic, assert, mapOptional, hashCodeInit, addHashCode } from "./Support";
import { TypeRef } from "./TypeBuilder";
import { TypeReconstituter, BaseGraphRewriteBuilder } from "./GraphRewriting";
import { TypeNames, namesTypeAttributeKind } from "./TypeNames";
@ -13,6 +13,7 @@ export type PrimitiveStringTypeKind = "string" | "date" | "time" | "date-time";
export type PrimitiveTypeKind = "none" | "any" | "null" | "bool" | "integer" | "double" | PrimitiveStringTypeKind;
export type NamedTypeKind = "class" | "enum" | "union";
export type TypeKind = PrimitiveTypeKind | NamedTypeKind | "array" | "object" | "map" | "intersection";
export type ObjectTypeKind = "object" | "map" | "class";
export function isPrimitiveStringTypeKind(kind: TypeKind): kind is PrimitiveStringTypeKind {
return kind === "string" || kind === "date" || kind === "time" || kind === "date-time";
@ -46,17 +47,23 @@ function orderedSetUnion<T>(sets: OrderedSet<OrderedSet<T>>): OrderedSet<T> {
export type Transformer = "parseInteger";
export class Transformation {
constructor(readonly transformer: Transformer, private _targetRef?: TypeRef) {}
private getTargetRef(): TypeRef {
if (this._targetRef === undefined) {
return panic("Target type accessed before it was set");
}
return this._targetRef;
}
constructor(readonly transformer: Transformer, private readonly _targetRef: TypeRef) {}
get targetType(): Type {
return this.getTargetRef().deref()[0];
return this._targetRef.deref()[0];
}
equals(other: any): boolean {
if (!(other instanceof Transformation)) return false;
if (this.transformer !== other.transformer) return false;
return this._targetRef.equals(other._targetRef);
}
hashCode(): number {
let hashCode = hashCodeInit;
hashCode = addHashCode(hashCode, hash(this.transformer));
hashCode = addHashCode(hashCode, hash(this._targetRef));
return hashCode;
}
}
@ -106,7 +113,9 @@ export abstract class Type {
}
abstract get isNullable(): boolean;
// FIXME: Remove `isPrimitive`
abstract isPrimitive(): this is PrimitiveType;
abstract get identity(): List<any> | undefined;
abstract reconstitute<T extends BaseGraphRewriteBuilder>(builder: TypeReconstituter<T>): void;
protected reconstituteTransformation<T extends BaseGraphRewriteBuilder>(
@ -227,6 +236,10 @@ export abstract class Type {
}
}
export function primitiveTypeIdentity(kind: PrimitiveTypeKind, transformation: Transformation | undefined): List<any> {
return List([kind, transformation]);
}
export class PrimitiveType extends Type {
// @ts-ignore: This is initialized in the Type constructor
readonly kind: PrimitiveTypeKind;
@ -246,12 +259,28 @@ export class PrimitiveType extends Type {
return true;
}
get identity(): List<any> | undefined {
return primitiveTypeIdentity(this.kind, this.transformation);
}
reconstitute<T extends BaseGraphRewriteBuilder>(builder: TypeReconstituter<T>): void {
builder.getPrimitiveType(this.kind, this.reconstituteTransformation(builder));
}
}
export function stringTypeIdentity(
enumCases: OrderedMap<string, number> | undefined,
transformation: Transformation | undefined
): List<any> | undefined {
if (enumCases !== undefined) return undefined;
// mapOptional(ec => ec.keySeq().toSet(), enumCases)
return List(["string", transformation]);
}
export class StringType extends PrimitiveType {
// @ts-ignore: This is initialized in the Type constructor
readonly kind: "string";
constructor(
typeRef: TypeRef,
readonly enumCases: OrderedMap<string, number> | undefined,
@ -260,6 +289,10 @@ export class StringType extends PrimitiveType {
super(typeRef, "string", transformation, false);
}
get identity(): List<any> | undefined {
return stringTypeIdentity(this.enumCases, this.transformation);
}
reconstitute<T extends BaseGraphRewriteBuilder>(builder: TypeReconstituter<T>): void {
builder.getStringType(this.enumCases, this.reconstituteTransformation(builder));
}
@ -272,6 +305,10 @@ export class StringType extends PrimitiveType {
}
}
export function arrayTypeIdentity(itemsRef: TypeRef, transformation: Transformation | undefined): List<any> {
return List(["array", transformation, itemsRef]);
}
export class ArrayType extends Type {
// @ts-ignore: This is initialized in the Type constructor
readonly kind: "array";
@ -311,6 +348,10 @@ export class ArrayType extends Type {
return false;
}
get identity(): List<any> {
return arrayTypeIdentity(this.getItemsRef(), this.transformation);
}
reconstitute<T extends BaseGraphRewriteBuilder>(builder: TypeReconstituter<T>): void {
const itemsRef = this.getItemsRef();
const maybeItems = builder.lookup(itemsRef);
@ -356,10 +397,36 @@ export class ClassProperty extends GenericClassProperty<TypeRef> {
}
}
function objectTypeIdentify(
kind: ObjectTypeKind,
properties: OrderedMap<string, ClassProperty>,
additionalPropertiesRef: TypeRef | undefined,
transformation: Transformation | undefined
): List<any> {
return List([kind, transformation, properties, additionalPropertiesRef]);
}
export function classTypeIdentity(
properties: OrderedMap<string, ClassProperty>,
transformation: Transformation | undefined
): List<any> {
return objectTypeIdentify("class", properties, undefined, transformation);
}
export function mapTypeIdentify(
additionalPropertiesRef: TypeRef | undefined,
transformation: Transformation | undefined
): List<any> {
return objectTypeIdentify("map", OrderedMap(), additionalPropertiesRef, transformation);
}
export class ObjectType extends Type {
// @ts-ignore: This is initialized in the Type constructor
readonly kind: ObjectTypeKind;
constructor(
typeRef: TypeRef,
kind: TypeKind,
kind: ObjectTypeKind,
readonly isFixed: boolean,
private _properties: OrderedMap<string, ClassProperty> | undefined,
private _additionalPropertiesRef: TypeRef | undefined,
@ -367,7 +434,6 @@ export class ObjectType extends Type {
) {
super(typeRef, kind, transformation);
assert(kind === "object" || kind === "map" || kind === "class");
if (kind === "map") {
if (_properties !== undefined) {
assert(_properties.isEmpty());
@ -385,6 +451,8 @@ export class ObjectType extends Type {
additionalPropertiesRef: TypeRef | undefined,
transformation: Transformation | undefined
) {
assert (this._properties === undefined, "Tried to set object properties twice");
if (this instanceof MapType) {
assert(properties.isEmpty(), "Cannot set properties on map type");
} else if (this._properties !== undefined) {
@ -412,10 +480,15 @@ export class ObjectType extends Type {
return OrderedMap(props);
}
getAdditionalProperties(): Type | undefined {
private getAdditionalPropertiesRef(): TypeRef | undefined {
assert(this._properties !== undefined, "Properties are not set yet");
if (this._additionalPropertiesRef === undefined) return undefined;
return this._additionalPropertiesRef.deref()[0];
return this._additionalPropertiesRef;
}
getAdditionalProperties(): Type | undefined {
const tref = this.getAdditionalPropertiesRef();
if (tref === undefined) return undefined;
return tref.deref()[0];
}
getChildren(): OrderedSet<Type> {
@ -437,6 +510,11 @@ export class ObjectType extends Type {
return false;
}
get identity(): List<any> | undefined {
if (this.isFixed) return undefined;
return objectTypeIdentify(this.kind, this.getProperties(), this.getAdditionalPropertiesRef(), this.transformation);
}
reconstitute<T extends BaseGraphRewriteBuilder>(builder: TypeReconstituter<T>): void {
const maybePropertyTypes = builder.lookup(this.getProperties().map(cp => cp.typeRef));
const maybeAdditionalProperties = mapOptional(r => builder.lookup(r), this._additionalPropertiesRef);
@ -535,7 +613,7 @@ export class MapType extends ObjectType {
readonly kind: "map";
constructor(typeRef: TypeRef, valuesRef: TypeRef | undefined, transformation: Transformation | undefined) {
super(typeRef, "map", false, OrderedMap(), valuesRef, transformation);
super(typeRef, "map", false, mapOptional(() => OrderedMap(), valuesRef), valuesRef, transformation);
}
// FIXME: Remove and use `getAdditionalProperties()` instead.
@ -544,6 +622,10 @@ export class MapType extends ObjectType {
}
}
export function enumTypeIdentity(cases: OrderedSet<string>, transformation: Transformation | undefined): List<any> {
return List([transformation, cases.toSet()]);
}
export class EnumType extends Type {
// @ts-ignore: This is initialized in the Type constructor
kind: "enum";
@ -560,6 +642,10 @@ export class EnumType extends Type {
return false;
}
get identity(): List<any> {
return enumTypeIdentity(this.cases, this.transformation);
}
reconstitute<T extends BaseGraphRewriteBuilder>(builder: TypeReconstituter<T>): void {
builder.getEnumType(this.cases, this.reconstituteTransformation(builder));
}
@ -586,6 +672,28 @@ export function setOperationCasesEqual(
return !failed;
}
export function setOperationTypeIdentity(
kind: TypeKind,
memberRefs: OrderedSet<TypeRef>,
transformation: Transformation | undefined
): List<any> {
return List([kind, transformation, memberRefs]);
}
export function unionTypeIdentity(
memberRefs: OrderedSet<TypeRef>,
transformation: Transformation | undefined
): List<any> {
return setOperationTypeIdentity("union", memberRefs, transformation);
}
export function intersectionTypeIdentity(
memberRefs: OrderedSet<TypeRef>,
transformation: Transformation | undefined
): List<any> {
return setOperationTypeIdentity("intersection", memberRefs, transformation);
}
export abstract class SetOperationType extends Type {
constructor(
typeRef: TypeRef,
@ -631,6 +739,10 @@ export abstract class SetOperationType extends Type {
return false;
}
get identity(): List<any> {
return setOperationTypeIdentity(this.kind, this.getMemberRefs(), this.transformation);
}
protected structuralEqualityStep(other: SetOperationType, queue: (a: Type, b: Type) => boolean): boolean {
return super.structuralEqualityStep(other, queue) && setOperationCasesEqual(this.members, other.members, queue);
}

Просмотреть файл

@ -1,6 +1,6 @@
"use strict";
import { Map, OrderedMap, OrderedSet, Set } from "immutable";
import { Map, OrderedMap, OrderedSet, Set, List } from "immutable";
import {
PrimitiveTypeKind,
@ -16,12 +16,20 @@ import {
ClassProperty,
IntersectionType,
ObjectType,
Transformation
Transformation,
stringTypeIdentity,
primitiveTypeIdentity,
enumTypeIdentity,
mapTypeIdentify,
arrayTypeIdentity,
classTypeIdentity,
unionTypeIdentity,
intersectionTypeIdentity
} from "./Type";
import { removeNullFromUnion } from "./TypeUtils";
import { TypeGraph } from "./TypeGraph";
import { TypeAttributes, combineTypeAttributes, TypeAttributeKind } from "./TypeAttributes";
import { defined, assert, panic, setUnion, mapOptional, ifUndefined } from "./Support";
import { defined, assert, panic, setUnion, mapOptional } from "./Support";
export class TypeRef {
constructor(readonly graph: TypeGraph, readonly index: number) {}
@ -195,15 +203,35 @@ export class TypeBuilder {
}
// FIXME: make mutable?
private _primitiveTypes: Map<PrimitiveTypeKind, TypeRef> = Map();
private _noEnumStringType: TypeRef | undefined = undefined;
// FIXME: Combine map and object type maps
private _mapTypes: Map<TypeRef, TypeRef> = Map();
private _arrayTypes: Map<TypeRef, TypeRef> = Map();
private _enumTypes: Map<Set<string>, TypeRef> = Map();
private _classTypes: Map<Map<string, ClassProperty>, TypeRef> = Map();
private _unionTypes: Map<Set<TypeRef>, TypeRef> = Map();
private _intersectionTypes: Map<Set<TypeRef>, TypeRef> = Map();
private _typeForIdentity: Map<List<any>, TypeRef> = Map();
private registerTypeForIdentity(identity: List<any> | undefined, tref: TypeRef): void {
if (identity === undefined) return;
this._typeForIdentity = this._typeForIdentity.set(identity, tref);
}
private getOrAddType(identity: List<any> | undefined, creator: (tr: TypeRef) => Type, attributes: TypeAttributes | undefined, forwardingRef: TypeRef | undefined): TypeRef {
let maybeTypeRef: TypeRef | undefined;
if (identity === undefined) {
maybeTypeRef = undefined;
} else {
maybeTypeRef = this._typeForIdentity.get(identity);
}
if (maybeTypeRef !== undefined) {
const result = this.forwardIfNecessary(forwardingRef, maybeTypeRef);
this.addAttributes(result, attributes);
return result;
}
const tref = this.addType(forwardingRef, creator, attributes);
this.registerTypeForIdentity(identity, tref);
return tref;
}
private registerType(t: Type): void {
this.registerTypeForIdentity(t.identity, t.typeRef);
}
getPrimitiveType(kind: PrimitiveTypeKind, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef {
assert(kind !== "string", "Use getStringType to create strings");
@ -213,14 +241,11 @@ export class TypeBuilder {
if (kind === "string") {
return this.getStringType(undefined, undefined, transformation, forwardingRef);
}
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._primitiveTypes.get(kind)));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new PrimitiveType(tr, kind, transformation), undefined);
if (transformation === undefined) {
this._primitiveTypes = this._primitiveTypes.set(kind, tref);
}
}
return tref;
return this.getOrAddType(primitiveTypeIdentity(kind, transformation),
tr => new PrimitiveType(tr, kind, transformation),
undefined,
forwardingRef
);
}
getStringType(
@ -229,48 +254,21 @@ export class TypeBuilder {
transformation?: Transformation,
forwardingRef?: TypeRef
): TypeRef {
if (cases === undefined) {
// FIXME: Right now we completely ignore names for strings
// without enum cases. That's the correct behavior at the time,
// because string types never are assigned names, but we might
// do that at some point, but in that case we'll want a different
// type for each occurrence, not the same single string type with
// all the names.
//
// The proper solution at that point might be to just figure
// out whether we do want string types to have names (we most
// likely don't), and if not, still don't keep track of them.
let result: TypeRef;
if (this._noEnumStringType === undefined || transformation !== undefined) {
result = this.addType(
forwardingRef,
tr => new StringType(tr, undefined, transformation),
undefined
);
if (transformation === undefined) {
this._noEnumStringType = result;
}
} else {
result = this.forwardIfNecessary(forwardingRef, this._noEnumStringType);
}
this.addAttributes(result, attributes);
return result;
}
return this.addType(forwardingRef, tr => new StringType(tr, cases, transformation), attributes);
return this.getOrAddType(
stringTypeIdentity(cases, transformation),
tr => new StringType(tr, cases, transformation),
attributes,
forwardingRef
);
}
getEnumType(attributes: TypeAttributes, cases: OrderedSet<string>, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef {
const unorderedCases = cases.toSet();
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._enumTypes.get(unorderedCases)));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new EnumType(tr, cases, transformation), attributes);
if (transformation === undefined) {
this._enumTypes = this._enumTypes.set(unorderedCases, tref);
}
} else {
this.addAttributes(tref, attributes);
}
return tref;
return this.getOrAddType(
enumTypeIdentity(cases, transformation),
tr => new EnumType(tr, cases, transformation),
attributes,
forwardingRef
);
}
getUniqueObjectType(
@ -292,25 +290,13 @@ export class TypeBuilder {
return this.addType(forwardingRef, tr => new MapType(tr, undefined, undefined), undefined);
}
private registerMapType(values: TypeRef, tref: TypeRef): void {
if (this._mapTypes.has(values)) return;
this._mapTypes = this._mapTypes.set(values, tref);
}
getMapType(values: TypeRef, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef {
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._mapTypes.get(values)));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new MapType(tr, values, transformation), undefined);
if (transformation === undefined) {
this.registerMapType(values, tref);
}
}
return tref;
}
private registerClassType(properties: OrderedMap<string, ClassProperty>, tref: TypeRef): void {
const map = properties.toMap();
this._classTypes = this._classTypes.set(map, tref);
return this.getOrAddType(
mapTypeIdentify(values, transformation),
tr => new MapType(tr, values, transformation),
undefined,
forwardingRef
);
}
setObjectProperties(
@ -324,35 +310,20 @@ export class TypeBuilder {
return panic("Tried to set properties of non-object type");
}
type.setProperties(this.modifyPropertiesIfNecessary(properties), additionalProperties, transformation);
if (transformation !== undefined) return;
if (type instanceof MapType) {
this.registerMapType(defined(additionalProperties), ref);
} else if (type instanceof ClassType) {
if (!type.isFixed) {
this.registerClassType(properties, ref);
}
}
this.registerType(type);
}
getUniqueArrayType(forwardingRef?: TypeRef): TypeRef {
return this.addType(forwardingRef, tr => new ArrayType(tr, undefined, undefined), undefined);
}
private registerArrayType(items: TypeRef, tref: TypeRef): void {
if (this._arrayTypes.has(items)) return;
this._arrayTypes = this._arrayTypes.set(items, tref);
}
getArrayType(items: TypeRef, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef {
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._arrayTypes.get(items)));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new ArrayType(tr, items, transformation), undefined);
if (transformation === undefined) {
this.registerArrayType(items, tref);
}
}
return tref;
return this.getOrAddType(
arrayTypeIdentity(items, transformation),
tr => new ArrayType(tr, items, transformation),
undefined,
forwardingRef
);
}
setArrayItems(ref: TypeRef, items: TypeRef, transformation?: Transformation): void {
@ -361,9 +332,7 @@ export class TypeBuilder {
return panic("Tried to set items of non-array type");
}
type.setItems(items, transformation);
if (transformation === undefined) {
this.registerArrayType(items, ref);
}
this.registerType(type);
}
modifyPropertiesIfNecessary(properties: OrderedMap<string, ClassProperty>): OrderedMap<string, ClassProperty> {
@ -382,22 +351,12 @@ export class TypeBuilder {
transformation?: Transformation,
forwardingRef?: TypeRef
): TypeRef {
properties = this.modifyPropertiesIfNecessary(properties);
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._classTypes.get(properties.toMap())));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new ClassType(tr, false, properties, transformation), attributes);
if (transformation === undefined) {
this.registerClassType(properties, tref);
}
} else {
this.addAttributes(tref, attributes);
}
return tref;
}
private registerUnionType(members: Set<TypeRef>, tref: TypeRef): void {
if (this._unionTypes.has(members)) return;
this._unionTypes = this._unionTypes.set(members, tref);
return this.getOrAddType(
classTypeIdentity(properties, transformation),
tr => new ClassType(tr, false, properties, transformation),
attributes,
forwardingRef
);
}
// FIXME: Maybe just distinguish between this and `getClassType`
@ -414,19 +373,15 @@ export class TypeBuilder {
}
getUnionType(attributes: TypeAttributes, members: OrderedSet<TypeRef>, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef {
const unorderedMembers = members.toSet();
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._unionTypes.get(unorderedMembers)));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new UnionType(tr, members, transformation), attributes);
if (transformation === undefined) {
this.registerUnionType(unorderedMembers, tref);
}
} else {
this.addAttributes(tref, attributes);
}
return tref;
return this.getOrAddType(
unionTypeIdentity(members, transformation),
tr => new UnionType(tr, members, transformation),
attributes,
forwardingRef
);
}
// FIXME: why do we sometimes call this with defined members???
getUniqueUnionType(
attributes: TypeAttributes,
members: OrderedSet<TypeRef> | undefined,
@ -436,25 +391,16 @@ export class TypeBuilder {
return this.addType(forwardingRef, tref => new UnionType(tref, members, transformation), attributes);
}
private registerIntersectionType(members: Set<TypeRef>, tref: TypeRef): void {
if (this._intersectionTypes.has(members)) return;
this._intersectionTypes = this._intersectionTypes.set(members, tref);
}
getIntersectionType(attributes: TypeAttributes, members: OrderedSet<TypeRef>, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef {
const unorderedMembers = members.toSet();
let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._intersectionTypes.get(unorderedMembers)));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new IntersectionType(tr, members, transformation), attributes);
if (transformation === undefined) {
this.registerIntersectionType(unorderedMembers, tref);
}
} else {
this.addAttributes(tref, attributes);
}
return tref;
return this.getOrAddType(
intersectionTypeIdentity(members, transformation),
tr => new IntersectionType(tr, members, transformation),
attributes,
forwardingRef
);
}
// FIXME: why do we sometimes call this with defined members???
getUniqueIntersectionType(
attributes: TypeAttributes,
members: OrderedSet<TypeRef> | undefined,
@ -470,14 +416,7 @@ export class TypeBuilder {
return panic("Tried to set members of non-set-operation type");
}
type.setMembers(members, transformation);
const unorderedMembers = members.toSet();
if (type instanceof UnionType) {
this.registerUnionType(unorderedMembers, ref);
} else if (type instanceof IntersectionType) {
this.registerIntersectionType(unorderedMembers, ref);
} else {
return panic("Unknown set operation type ${type.kind}");
}
this.registerType(type);
}
setLostTypeAttributes(): void {

Просмотреть файл

@ -95,6 +95,7 @@ export const RustLanguage: Language = {
},
diffViaSchema: true,
skipDiffViaSchema: [
"combinations.json",
"bug427.json",
"keywords.json",
"recursive.json",
@ -203,6 +204,7 @@ export const GoLanguage: Language = {
},
diffViaSchema: true,
skipDiffViaSchema: [
"combinations.json",
"bug427.json",
"github-events.json",
"reddit.json",
@ -253,6 +255,7 @@ export const CPlusPlusLanguage: Language = {
diffViaSchema: true,
skipDiffViaSchema: [
"github-events.json",
"combinations.json",
"bug427.json",
"keywords.json",
"0a91a.json",
@ -358,6 +361,7 @@ export const SwiftLanguage: Language = {
"337ed.json",
"34702.json",
"7f568.json",
"734ad.json",
"76ae1.json",
"c8c7e.json",
"e53b5.json",