From 48b63c58935f07959e08ac83e120174414e27c6f Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Sat, 14 Apr 2018 08:04:59 -0700 Subject: [PATCH] WIP: Each type has an optional attached transformation --- src/CombineClasses.ts | 2 +- src/ConvenienceRenderer.ts | 5 +- src/DeclarationIR.ts | 4 +- src/GatherNames.ts | 12 +-- src/GraphRewriting.ts | 132 ++++++++++++++---------- src/InferEnums.ts | 11 +- src/InferMaps.ts | 3 +- src/Inference.ts | 6 +- src/Language/JSONSchema.ts | 1 - src/Language/JavaScript.ts | 2 +- src/Language/Ruby/index.ts | 6 +- src/Language/TypeScriptFlow.ts | 4 +- src/ReplaceObjectType.ts | 8 +- src/ResolveIntersections.ts | 12 +-- src/Support.ts | 8 ++ src/Type.ts | 170 +++++++++++++++++-------------- src/TypeBuilder.ts | 180 ++++++++++++++------------------- src/TypeGraph.ts | 15 +-- src/TypeUtils.ts | 10 +- src/UnifyClasses.ts | 9 +- src/UnionBuilder.ts | 12 +-- 21 files changed, 317 insertions(+), 295 deletions(-) diff --git a/src/CombineClasses.ts b/src/CombineClasses.ts index 2bf64f0c..18d57e83 100644 --- a/src/CombineClasses.ts +++ b/src/CombineClasses.ts @@ -115,7 +115,7 @@ export function findSimilarityCliques( ): ClassType[][] { let unprocessedClasses = graph .allNamedTypesSeparated() - .objects.filter(o => o instanceof ClassType && (includeFixedClasses || !o.isFixed)) + .objects.filter(o => o instanceof ClassType && o.transformation === undefined && (includeFixedClasses || !o.isFixed)) .toArray() as ClassType[]; const cliques: ClassType[][] = []; diff --git a/src/ConvenienceRenderer.ts b/src/ConvenienceRenderer.ts index bb36dc18..aef159e5 100644 --- a/src/ConvenienceRenderer.ts +++ b/src/ConvenienceRenderer.ts @@ -448,7 +448,7 @@ export abstract class ConvenienceRenderer extends Renderer { .sortBy((_, n) => defined(names.get(defined(propertyNameds.get(n))))); return sortedMap.toOrderedSet(); } - return t.children.toOrderedSet(); + return t.getChildren(); }; protected get namedUnions(): OrderedSet { @@ -524,7 +524,6 @@ export abstract class ConvenienceRenderer extends Renderer { }, _enumType => "enum", _unionType => "union", - transformedType => typeNameForUnionMember(transformedType.targetType), _dateType => "date", _timeType => "time", _dateTimeType => "date_time" @@ -898,7 +897,7 @@ export abstract class ConvenienceRenderer extends Renderer { function visit(t: Type) { if (visitedTypes.has(t)) return; - queue.push(...t.children.toArray()); + queue.push(...t.getChildren().toArray()); visitedTypes = visitedTypes.add(t); processed = processed.add(process(t)); } diff --git a/src/DeclarationIR.ts b/src/DeclarationIR.ts index 61068597..66f7ccac 100644 --- a/src/DeclarationIR.ts +++ b/src/DeclarationIR.ts @@ -54,7 +54,7 @@ export function cycleBreakerTypesForGraph( if (visitedTypes.has(t)) return; if (isImplicitCycleBreaker(t)) { - queue.push(...t.children.toArray()); + queue.push(...t.getChildren().toArray()); } else { const maybeBreaker = findBreaker(t, path, canBreakCycles); if (maybeBreaker !== undefined) { @@ -63,7 +63,7 @@ export function cycleBreakerTypesForGraph( } const pathForChildren = path.unshift(t); - t.children.forEach(c => visit(c, pathForChildren)); + t.getChildren().forEach(c => visit(c, pathForChildren)); } visitedTypes = visitedTypes.add(t); diff --git a/src/GatherNames.ts b/src/GatherNames.ts index 1fc2c11a..6b9153ee 100644 --- a/src/GatherNames.ts +++ b/src/GatherNames.ts @@ -130,11 +130,11 @@ export function gatherNames(graph: TypeGraph, debugPrint: boolean): void { members.forEach(memberType => { addNames(memberType, names); }); - }, - transformedType => { - addNames(transformedType.targetType, names); } ); + if (t.transformation !== undefined) { + addNames(t.transformation.targetType, names); + } } } @@ -245,11 +245,11 @@ export function gatherNames(graph: TypeGraph, debugPrint: boolean): void { const unionIsAncestor = unionHasGivenName || nullableFromUnion(unionType) === null; const ancestorForMembers = unionIsAncestor ? unionType : ancestor; members.forEach(memberType => processType(ancestorForMembers, memberType, undefined)); - }, - transformedType => { - processType(ancestor, transformedType.targetType, alternativeSuffix); } ); + if (t.transformation !== undefined) { + processType(ancestor, t.transformation.targetType, alternativeSuffix); + } } } diff --git a/src/GraphRewriting.ts b/src/GraphRewriting.ts index e7452360..a44ecda6 100644 --- a/src/GraphRewriting.ts +++ b/src/GraphRewriting.ts @@ -2,7 +2,7 @@ import { Map, OrderedMap, OrderedSet, Set, Collection, isCollection } from "immutable"; -import { PrimitiveTypeKind, Type, ClassProperty, Transformer } from "./Type"; +import { PrimitiveTypeKind, Type, ClassProperty, Transformation } from "./Type"; import { combineTypeAttributesOfTypes } from "./TypeUtils"; import { TypeGraph } from "./TypeGraph"; import { TypeAttributes } from "./TypeAttributes"; @@ -78,44 +78,57 @@ export class TypeReconstituter { return this._typeBuilder.reconstituteTypeRef(trefs); } - getPrimitiveType(kind: PrimitiveTypeKind): void { - this.registerAndAddAttributes(this.builderForNewType().getPrimitiveType(kind, this._forwardingRef)); + getPrimitiveType(kind: PrimitiveTypeKind, transformation: Transformation | undefined): void { + this.registerAndAddAttributes( + this.builderForNewType().getPrimitiveType(kind, transformation, this._forwardingRef) + ); } - getStringType(enumCases: OrderedMap | undefined): void { - this.register(this.builderForNewType().getStringType(this._typeAttributes, enumCases, this._forwardingRef)); + getStringType(enumCases: OrderedMap | undefined, transformation: Transformation | undefined): void { + this.register( + this.builderForNewType().getStringType(this._typeAttributes, enumCases, transformation, this._forwardingRef) + ); } - getEnumType(cases: OrderedSet): void { - this.register(this.builderForNewType().getEnumType(this._typeAttributes, cases, this._forwardingRef)); + getEnumType(cases: OrderedSet, transformation: Transformation | undefined): void { + this.register( + this.builderForNewType().getEnumType(this._typeAttributes, cases, transformation, this._forwardingRef) + ); } getUniqueMapType(): void { this.registerAndAddAttributes(this.builderForNewType().getUniqueMapType(this._forwardingRef)); } - getMapType(values: TypeRef): void { - this.registerAndAddAttributes(this.builderForNewType().getMapType(values, this._forwardingRef)); + getMapType(values: TypeRef, transformation: Transformation | undefined): void { + this.registerAndAddAttributes(this.builderForNewType().getMapType(values, transformation, this._forwardingRef)); } getUniqueArrayType(): void { this.registerAndAddAttributes(this.builderForNewType().getUniqueArrayType(this._forwardingRef)); } - getArrayType(items: TypeRef): void { - this.registerAndAddAttributes(this.builderForNewType().getArrayType(items, this._forwardingRef)); + getArrayType(items: TypeRef, transformation: Transformation | undefined): void { + this.registerAndAddAttributes( + this.builderForNewType().getArrayType(items, transformation, this._forwardingRef) + ); } - setArrayItems(items: TypeRef): void { - this.builderForSetting().setArrayItems(this.getResult(), items); + setArrayItems(items: TypeRef, transformation: Transformation | undefined): void { + this.builderForSetting().setArrayItems(this.getResult(), items, transformation); } - getObjectType(properties: OrderedMap, additionalProperties: TypeRef | undefined): void { + getObjectType( + properties: OrderedMap, + additionalProperties: TypeRef | undefined, + transformation: Transformation | undefined + ): void { this.register( this.builderForNewType().getUniqueObjectType( this._typeAttributes, properties, additionalProperties, + transformation, this._forwardingRef ) ); @@ -123,83 +136,98 @@ export class TypeReconstituter { getUniqueObjectType( properties: OrderedMap | undefined, - additionalProperties: TypeRef | undefined + additionalProperties: TypeRef | undefined, + transformation: Transformation | undefined ): void { this.register( this.builderForNewType().getUniqueObjectType( this._typeAttributes, properties, additionalProperties, + transformation, this._forwardingRef ) ); } - getClassType(properties: OrderedMap): void { + getClassType(properties: OrderedMap, transformation: Transformation | undefined): void { if (this._makeClassUnique) { - this.getUniqueClassType(false, properties); + this.getUniqueClassType(false, properties, transformation); return; } - this.register(this.builderForNewType().getClassType(this._typeAttributes, properties, this._forwardingRef)); + this.register(this.builderForNewType().getClassType(this._typeAttributes, properties, transformation, this._forwardingRef)); } - getUniqueClassType(isFixed: boolean, properties: OrderedMap | undefined): void { + getUniqueClassType( + isFixed: boolean, + properties: OrderedMap | undefined, + transformation: Transformation | undefined + ): void { this.register( - this.builderForNewType().getUniqueClassType(this._typeAttributes, isFixed, properties, this._forwardingRef) + this.builderForNewType().getUniqueClassType( + this._typeAttributes, + isFixed, + properties, + transformation, + this._forwardingRef + ) ); } setObjectProperties( properties: OrderedMap, - additionalProperties: TypeRef | undefined + additionalProperties: TypeRef | undefined, + transformation: Transformation | undefined ): void { - this.builderForSetting().setObjectProperties(this.getResult(), properties, additionalProperties); + this.builderForSetting().setObjectProperties( + this.getResult(), + properties, + additionalProperties, + transformation + ); } - getUnionType(members: OrderedSet): void { - this.register(this.builderForNewType().getUnionType(this._typeAttributes, members, this._forwardingRef)); + getUnionType(members: OrderedSet, transformation: Transformation | undefined): void { + this.register( + this.builderForNewType().getUnionType(this._typeAttributes, members, transformation, this._forwardingRef) + ); } getUniqueUnionType(): void { this.register( - this.builderForNewType().getUniqueUnionType(this._typeAttributes, undefined, this._forwardingRef) - ); - } - - getIntersectionType(members: OrderedSet): void { - this.register(this.builderForNewType().getIntersectionType(this._typeAttributes, members, this._forwardingRef)); - } - - getUniqueIntersectionType(members?: OrderedSet): void { - this.register( - this.builderForNewType().getUniqueIntersectionType(this._typeAttributes, members, this._forwardingRef) - ); - } - - setSetOperationMembers(members: OrderedSet): void { - this.builderForSetting().setSetOperationMembers(this.getResult(), members); - } - - getTransformedType(transformer: Transformer, sourceRef: TypeRef, targetRef: TypeRef): void { - this.register( - this.builderForNewType().getTransformedType( + this.builderForNewType().getUniqueUnionType( this._typeAttributes, - transformer, - sourceRef, - targetRef, + undefined, + undefined, this._forwardingRef ) ); } - getUniqueTransformedType(transformer: Transformer): void { + getIntersectionType(members: OrderedSet, transformation: Transformation | undefined): void { this.register( - this.builderForNewType().getUniqueTransformedType(this._typeAttributes, transformer, this._forwardingRef) + this.builderForNewType().getIntersectionType( + this._typeAttributes, + members, + transformation, + this._forwardingRef + ) ); } - setTransformedTypeTypes(sourceRef: TypeRef, targetRef: TypeRef): void { - this.builderForSetting().setTransformedTypeTypes(this.getResult(), sourceRef, targetRef); + getUniqueIntersectionType(): void { + this.register( + this.builderForNewType().getUniqueIntersectionType( + this._typeAttributes, + undefined, + undefined, + this._forwardingRef + ) + ); + } + + setSetOperationMembers(members: OrderedSet, transformation: Transformation | undefined): void { + this.builderForSetting().setSetOperationMembers(this.getResult(), members, transformation); } } diff --git a/src/InferEnums.ts b/src/InferEnums.ts index 443aff30..d10efce8 100644 --- a/src/InferEnums.ts +++ b/src/InferEnums.ts @@ -34,15 +34,15 @@ function replaceString( const attributes = t.getAttributes(); const maybeEnumCases = shouldBeEnum(t); if (maybeEnumCases !== undefined) { - return builder.getEnumType(attributes, maybeEnumCases.keySeq().toOrderedSet(), forwardingRef); + return builder.getEnumType(attributes, maybeEnumCases.keySeq().toOrderedSet(), undefined, forwardingRef); } - return builder.getStringType(attributes, undefined, forwardingRef); + return builder.getStringType(attributes, undefined, undefined, forwardingRef); } // A union needs replacing if it contains more than one string type, one of them being // a basic string type. function unionNeedsReplacing(u: UnionType): OrderedSet | undefined { - const stringMembers = u.stringTypeMembers; + const stringMembers = u.stringTypeMembers.filter(t => t.transformation === undefined); if (stringMembers.size <= 1) return undefined; if (u.findMember("string") === undefined) return undefined; return stringMembers; @@ -63,11 +63,12 @@ function replaceUnion(group: Set, builder: GraphRewriteBuilder t instanceof StringType) + .filter(t => t instanceof StringType && t.transformation === undefined) .map(t => [t]) .toArray() as StringType[][]; return graph.rewrite("infer enums", stringTypeMapping, false, allStrings, debugPrintReconstitution, replaceString); diff --git a/src/InferMaps.ts b/src/InferMaps.ts index f8f7ea62..2a17ed59 100644 --- a/src/InferMaps.ts +++ b/src/InferMaps.ts @@ -121,6 +121,7 @@ export function inferMaps( unionBuilderForUnification(builder, false, false, false, conflateNumbers), conflateNumbers ), + undefined, forwardingRef ); builder.addAttributes(tref, c.getAttributes()); @@ -131,7 +132,7 @@ export function inferMaps( ClassType >; const classesToReplace = allClasses - .filter(c => !c.isFixed && shouldBeMap(c.getProperties()) !== undefined) + .filter(c => c.transformation === undefined && !c.isFixed && shouldBeMap(c.getProperties()) !== undefined) .toArray(); return graph.rewrite( "infer maps", diff --git a/src/Inference.ts b/src/Inference.ts index ba95b48c..b90aaf74 100644 --- a/src/Inference.ts +++ b/src/Inference.ts @@ -49,7 +49,7 @@ class InferenceUnionBuilder extends UnionBuilder [c, counts[c]])); - return this.typeBuilder.getStringType(typeAttributes, caseMap, forwardingRef); + return this.typeBuilder.getStringType(typeAttributes, caseMap, undefined, forwardingRef); } protected makeObject( @@ -179,9 +179,9 @@ export class TypeInference { const propertyMap = OrderedMap(properties); if (fixed) { - return this._typeBuilder.getUniqueClassType(typeAttributes, true, propertyMap, forwardingRef); + return this._typeBuilder.getUniqueClassType(typeAttributes, true, propertyMap, undefined, forwardingRef); } else { - return this._typeBuilder.getClassType(typeAttributes, propertyMap, forwardingRef); + return this._typeBuilder.getClassType(typeAttributes, propertyMap, undefined, forwardingRef); } } } diff --git a/src/Language/JSONSchema.ts b/src/Language/JSONSchema.ts index e0510c6c..e6f09488 100644 --- a/src/Language/JSONSchema.ts +++ b/src/Language/JSONSchema.ts @@ -130,7 +130,6 @@ export class JSONSchemaRenderer extends ConvenienceRenderer { return this.definitionForUnion(unionType); } }, - transformedType => this.schemaForType(transformedType.sourceType), _dateType => ({ type: "string", format: "date" }), _timeType => ({ type: "string", format: "time" }), _dateTimeType => ({ type: "string", format: "date-time" }) diff --git a/src/Language/JavaScript.ts b/src/Language/JavaScript.ts index d5e8df28..bb773a3b 100644 --- a/src/Language/JavaScript.ts +++ b/src/Language/JavaScript.ts @@ -163,7 +163,7 @@ export class JavaScriptRenderer extends ConvenienceRenderer { mapType => ["m(", this.typeMapTypeFor(mapType.values), ")"], _enumType => panic("We handled this above"), unionType => { - const children = unionType.children.map(this.typeMapTypeFor); + const children = unionType.getChildren().map(this.typeMapTypeFor); return ["u(", ...intercalate(", ", children).toArray(), ")"]; } ); diff --git a/src/Language/Ruby/index.ts b/src/Language/Ruby/index.ts index 62ecb693..c41d697c 100644 --- a/src/Language/Ruby/index.ts +++ b/src/Language/Ruby/index.ts @@ -487,7 +487,7 @@ export class RubyRenderer extends ConvenienceRenderer { this.emitDescription(this.descriptionForType(u)); this.emitBlock(["class ", unionName, " < Dry::Struct"], () => { const table: Sourcelike[][] = []; - this.forEachUnionMember(u, u.children, "none", null, (name, t) => { + this.forEachUnionMember(u, u.getChildren(), "none", null, (name, t) => { table.push([["attribute :", name, ", "], [this.dryType(t, true)]]); }); this.emitTable(table); @@ -499,8 +499,8 @@ export class RubyRenderer extends ConvenienceRenderer { this.ensureBlankLine(); const [maybeNull, nonNulls] = removeNullFromUnion(u, false); this.emitBlock("def self.from_dynamic!(d)", () => { - const memberNames = u.children.map((member: Type) => this.nameForUnionMember(u, member)); - this.forEachUnionMember(u, u.children, "none", null, (name, t) => { + const memberNames = u.getChildren().map((member: Type) => this.nameForUnionMember(u, member)); + this.forEachUnionMember(u, u.getChildren(), "none", null, (name, t) => { const nilMembers = memberNames .remove(name) .toArray() diff --git a/src/Language/TypeScriptFlow.ts b/src/Language/TypeScriptFlow.ts index c7758ad8..132f6d7d 100644 --- a/src/Language/TypeScriptFlow.ts +++ b/src/Language/TypeScriptFlow.ts @@ -91,7 +91,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { _enumType => panic("We handled this above"), unionType => { if (this._inlineUnions || nullableFromUnion(unionType) !== null) { - const children = unionType.children.map(c => parenIfNeeded(this.sourceFor(c))); + const children = unionType.getChildren().map(c => parenIfNeeded(this.sourceFor(c))); return multiWord(" | ", ...children.toArray()); } else { return singleWord(this.nameForNamedType(unionType)); @@ -123,7 +123,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { this.emitDescription(this.descriptionForType(u)); - const children = multiWord(" | ", ...u.children.map(c => parenIfNeeded(this.sourceFor(c))).toArray()); + const children = multiWord(" | ", ...u.getChildren().map(c => parenIfNeeded(this.sourceFor(c))).toArray()); this.emitLine("export type ", unionName, " = ", children.source, ";"); } diff --git a/src/ReplaceObjectType.ts b/src/ReplaceObjectType.ts index a9249c29..7f867d1e 100644 --- a/src/ReplaceObjectType.ts +++ b/src/ReplaceObjectType.ts @@ -31,7 +31,7 @@ export function replaceObjectType( } function makeClass(): TypeRef { - return builder.getUniqueClassType(attributes, true, reconstituteProperties(), forwardingRef); + return builder.getUniqueClassType(attributes, true, reconstituteProperties(), undefined, forwardingRef); } function reconstituteAdditionalProperties(): TypeRef { @@ -43,7 +43,7 @@ export function replaceObjectType( } if (properties.isEmpty()) { - const tref = builder.getMapType(reconstituteAdditionalProperties(), forwardingRef); + const tref = builder.getMapType(reconstituteAdditionalProperties(), undefined, forwardingRef); builder.addAttributes(tref, attributes); return tref; } @@ -77,12 +77,12 @@ export function replaceObjectType( */ } - const mapType = builder.getMapType(union, forwardingRef); + const mapType = builder.getMapType(union, undefined, forwardingRef); builder.addAttributes(mapType, attributes); return mapType; } - const allObjectTypes = graph.allTypesUnordered().filter(t => t.kind === "object") as Set; + const allObjectTypes = graph.allTypesUnordered().filter(t => t.kind === "object" && t.transformation === undefined) as Set; const objectTypesToReplace = leaveFullObjects ? allObjectTypes.filter(o => o.getProperties().isEmpty() || o.getAdditionalProperties() === undefined) : allObjectTypes; diff --git a/src/ResolveIntersections.ts b/src/ResolveIntersections.ts index 2c053110..550e47a9 100644 --- a/src/ResolveIntersections.ts +++ b/src/ResolveIntersections.ts @@ -222,6 +222,7 @@ class IntersectionAccumulator } addType(t: Type): TypeAttributes { + assert(t.transformation === undefined, "We don't support intersections of transformations yet"); let attributes = t.getAttributes(); matchTypeExhaustive( t, @@ -247,9 +248,6 @@ class IntersectionAccumulator ); this.addUnionSet(unionType.members); }, - _transformedType => { - return panic("We don't support intersections with transformed types yet"); - }, dateType => this.addUnionSet(OrderedSet([dateType])), timeType => this.addUnionSet(OrderedSet([timeType])), dateTimeType => this.addUnionSet(OrderedSet([dateTimeType])) @@ -367,7 +365,7 @@ class IntersectionUnionBuilder extends UnionBuilder< typeAttributes: TypeAttributes, forwardingRef: TypeRef | undefined ): TypeRef { - return this.typeBuilder.getEnumType(typeAttributes, OrderedSet(cases), forwardingRef); + return this.typeBuilder.getEnumType(typeAttributes, OrderedSet(cases), undefined, forwardingRef); } protected makeObject( @@ -387,7 +385,7 @@ class IntersectionUnionBuilder extends UnionBuilder< maybeAdditionalProperties === undefined ? undefined : this.makeIntersection(maybeAdditionalProperties, emptyTypeAttributes); - return this.typeBuilder.getUniqueObjectType(typeAttributes, properties, additionalProperties, forwardingRef); + return this.typeBuilder.getUniqueObjectType(typeAttributes, properties, additionalProperties, undefined, forwardingRef); } protected makeArray( @@ -397,7 +395,7 @@ class IntersectionUnionBuilder extends UnionBuilder< ): TypeRef { // FIXME: attributes const itemsType = this.makeIntersection(arrays, Map()); - const tref = this.typeBuilder.getArrayType(itemsType, forwardingRef); + const tref = this.typeBuilder.getArrayType(itemsType, undefined, forwardingRef); this.typeBuilder.addAttributes(tref, typeAttributes); return tref; } @@ -414,7 +412,7 @@ export function resolveIntersections( const intersections = types.filter(t => t instanceof IntersectionType) as Set; const [members, intersectionAttributes] = setOperationMembersRecursively(intersections.toArray()); if (members.isEmpty()) { - const t = builder.getPrimitiveType("any", forwardingRef); + const t = builder.getPrimitiveType("any", undefined, forwardingRef); builder.addAttributes(t, intersectionAttributes); return t; } diff --git a/src/Support.ts b/src/Support.ts index 504cd079..d5f7d159 100644 --- a/src/Support.ts +++ b/src/Support.ts @@ -73,6 +73,14 @@ export function mapOptional(f: (x: T) => U, x: T | undefined): U | undefin return f(x); } +export function ifUndefined(x: any, f: () => T): T | undefined { + if (x === undefined) { + return f(); + } else { + return undefined; + } +} + export function defined(x: T | undefined): T { if (x !== undefined) return x; return panic("Defined value expected, but got undefined"); diff --git a/src/Type.ts b/src/Type.ts index a7e66dc8..e0259a99 100644 --- a/src/Type.ts +++ b/src/Type.ts @@ -61,9 +61,13 @@ export class Transformation { } export abstract class Type { - constructor(readonly typeRef: TypeRef, readonly kind: TypeKind, private _transformation?: Transformation) {} + constructor( + readonly typeRef: TypeRef, + readonly kind: TypeKind, + private _transformation: Transformation | undefined + ) {} - setTransformation(transformation: Transformation): void { + setTransformation(transformation: Transformation | undefined): void { assert(this._transformation === undefined, "Tried to set transformation twice"); this._transformation = transformation; } @@ -72,7 +76,7 @@ export abstract class Type { return this._transformation; } - get children(): OrderedSet { + getChildren(): OrderedSet { if (this._transformation === undefined) { return OrderedSet(); } @@ -82,7 +86,7 @@ export abstract class Type { directlyReachableTypes(setForType: (t: Type) => OrderedSet | null): OrderedSet { const set = setForType(this); if (set) return set; - return orderedSetUnion(this.children.map((t: Type) => t.directlyReachableTypes(setForType))); + return orderedSetUnion(this.getChildren().map((t: Type) => t.directlyReachableTypes(setForType))); } getAttributes(): TypeAttributes { @@ -103,15 +107,16 @@ export abstract class Type { abstract get isNullable(): boolean; abstract isPrimitive(): this is PrimitiveType; + abstract reconstitute(builder: TypeReconstituter): void; - reconstitute(builder: TypeReconstituter): void { + protected reconstituteTransformation( + builder: TypeReconstituter + ): Transformation | undefined { const tform = this._transformation; if (tform === undefined) { - builder.setTransformationAndFinish(); + return undefined; } - builder.setTransformationAndFinish( - new Transformation(tform.transformer, builder.reconstitute(tform.targetType.typeRef)) - ); + return new Transformation(tform.transformer, builder.reconstitute(tform.targetType.typeRef)); } get debugPrintKind(): string { @@ -226,15 +231,11 @@ export class PrimitiveType extends Type { // @ts-ignore: This is initialized in the Type constructor readonly kind: PrimitiveTypeKind; - constructor(typeRef: TypeRef, kind: PrimitiveTypeKind, checkKind: boolean = true) { + constructor(typeRef: TypeRef, kind: PrimitiveTypeKind, transformation: Transformation | undefined, checkKind: boolean = true) { if (checkKind) { assert(kind !== "string", "Cannot instantiate a PrimitiveType as string"); } - super(typeRef, kind); - } - - get children(): OrderedSet { - return OrderedSet(); + super(typeRef, kind, transformation); } get isNullable(): boolean { @@ -246,25 +247,21 @@ export class PrimitiveType extends Type { } reconstitute(builder: TypeReconstituter): void { - builder.getPrimitiveType(this.kind); - } - - protected structuralEqualityStep(_other: Type, _queue: (a: Type, b: Type) => boolean): boolean { - return true; + builder.getPrimitiveType(this.kind, this.reconstituteTransformation(builder)); } } export class StringType extends PrimitiveType { - constructor(typeRef: TypeRef, readonly enumCases: OrderedMap | undefined) { - super(typeRef, "string", false); + constructor( + typeRef: TypeRef, + readonly enumCases: OrderedMap | undefined, + transformation: Transformation | undefined + ) { + super(typeRef, "string", transformation, false); } reconstitute(builder: TypeReconstituter): void { - builder.getStringType(this.enumCases); - } - - protected structuralEqualityStep(_other: Type, _queue: (a: Type, b: Type) => boolean): boolean { - return true; + builder.getStringType(this.enumCases, this.reconstituteTransformation(builder)); } get debugPrintKind(): string { @@ -279,15 +276,16 @@ export class ArrayType extends Type { // @ts-ignore: This is initialized in the Type constructor readonly kind: "array"; - constructor(typeRef: TypeRef, private _itemsRef?: TypeRef) { - super(typeRef, "array"); + constructor(typeRef: TypeRef, private _itemsRef: TypeRef | undefined, transformation: Transformation | undefined) { + super(typeRef, "array", transformation); } - setItems(itemsRef: TypeRef) { + setItems(itemsRef: TypeRef, transformation: Transformation | undefined) { if (this._itemsRef !== undefined) { return panic("Can only set array items once"); } this._itemsRef = itemsRef; + super.setTransformation(transformation); } private getItemsRef(): TypeRef { @@ -301,8 +299,8 @@ export class ArrayType extends Type { return this.getItemsRef().deref()[0]; } - get children(): OrderedSet { - return OrderedSet([this.items]); + getChildren(): OrderedSet { + return super.getChildren().add(this.items); } get isNullable(): boolean { @@ -318,14 +316,14 @@ export class ArrayType extends Type { const maybeItems = builder.lookup(itemsRef); if (maybeItems === undefined) { builder.getUniqueArrayType(); - builder.setArrayItems(builder.reconstitute(itemsRef)); + builder.setArrayItems(builder.reconstitute(itemsRef), this.reconstituteTransformation(builder)); } else { - builder.getArrayType(maybeItems); + builder.getArrayType(maybeItems, this.reconstituteTransformation(builder)); } } protected structuralEqualityStep(other: ArrayType, queue: (a: Type, b: Type) => boolean): boolean { - return queue(this.items, other.items); + return super.structuralEqualityStep(other, queue) && queue(this.items, other.items); } } @@ -364,9 +362,10 @@ export class ObjectType extends Type { kind: TypeKind, readonly isFixed: boolean, private _properties: OrderedMap | undefined, - private _additionalPropertiesRef: TypeRef | undefined + private _additionalPropertiesRef: TypeRef | undefined, + transformation: Transformation | undefined ) { - super(typeRef, kind); + super(typeRef, kind, transformation); assert(kind === "object" || kind === "map" || kind === "class"); if (kind === "map") { @@ -381,7 +380,11 @@ export class ObjectType extends Type { } } - setProperties(properties: OrderedMap, additionalPropertiesRef: TypeRef | undefined) { + setProperties( + properties: OrderedMap, + additionalPropertiesRef: TypeRef | undefined, + transformation: Transformation | undefined + ) { if (this instanceof MapType) { assert(properties.isEmpty(), "Cannot set properties on map type"); } else if (this._properties !== undefined) { @@ -394,6 +397,8 @@ export class ObjectType extends Type { this._properties = properties; this._additionalPropertiesRef = additionalPropertiesRef; + + this.setTransformation(transformation); } getProperties(): OrderedMap { @@ -413,7 +418,7 @@ export class ObjectType extends Type { return this._additionalPropertiesRef.deref()[0]; } - get children(): OrderedSet { + getChildren(): OrderedSet { const children = this.getSortedProperties() .map(p => p.type) .toOrderedSet(); @@ -421,7 +426,7 @@ export class ObjectType extends Type { if (additionalProperties === undefined) { return children; } - return children.add(additionalProperties); + return super.getChildren().union(children.add(additionalProperties)); } get isNullable(): boolean { @@ -443,20 +448,21 @@ export class ObjectType extends Type { const properties = this.getProperties().map( (cp, n) => new ClassProperty(defined(maybePropertyTypes.get(n)), cp.isOptional) ); + const tform = this.reconstituteTransformation(builder); switch (this.kind) { case "object": assert(this.isFixed); - builder.getObjectType(properties, maybeAdditionalProperties); + builder.getObjectType(properties, maybeAdditionalProperties, tform); break; case "map": - builder.getMapType(defined(maybeAdditionalProperties)); + builder.getMapType(defined(maybeAdditionalProperties), tform); break; case "class": if (this.isFixed) { - builder.getUniqueClassType(true, properties); + builder.getUniqueClassType(true, properties, tform); } else { - builder.getClassType(properties); + builder.getClassType(properties, tform); } break; default: @@ -466,13 +472,13 @@ export class ObjectType extends Type { switch (this.kind) { case "object": assert(this.isFixed); - builder.getUniqueObjectType(undefined, undefined); + builder.getUniqueObjectType(undefined, undefined, undefined); break; case "map": builder.getUniqueMapType(); break; case "class": - builder.getUniqueClassType(this.isFixed, undefined); + builder.getUniqueClassType(this.isFixed, undefined, undefined); break; default: return panic(`Invalid object type kind ${this.kind}`); @@ -482,11 +488,13 @@ export class ObjectType extends Type { cp => new ClassProperty(builder.reconstitute(cp.typeRef), cp.isOptional) ); const additionalProperties = mapOptional(r => builder.reconstitute(r), this._additionalPropertiesRef); - builder.setObjectProperties(properties, additionalProperties); + builder.setObjectProperties(properties, additionalProperties, this.reconstituteTransformation(builder)); } } protected structuralEqualityStep(other: ObjectType, queue: (a: Type, b: Type) => boolean): boolean { + if (!super.structuralEqualityStep(other, queue)) return false; + const pa = this.getProperties(); const pb = other.getProperties(); if (pa.size !== pb.size) return false; @@ -512,8 +520,13 @@ export class ClassType extends ObjectType { // @ts-ignore: This is initialized in the Type constructor kind: "class"; - constructor(typeRef: TypeRef, isFixed: boolean, properties: OrderedMap | undefined) { - super(typeRef, "class", isFixed, properties, undefined); + constructor( + typeRef: TypeRef, + isFixed: boolean, + properties: OrderedMap | undefined, + transformation: Transformation | undefined + ) { + super(typeRef, "class", isFixed, properties, undefined, transformation); } } @@ -521,8 +534,8 @@ export class MapType extends ObjectType { // @ts-ignore: This is initialized in the Type constructor readonly kind: "map"; - constructor(typeRef: TypeRef, valuesRef: TypeRef | undefined) { - super(typeRef, "map", false, OrderedMap(), valuesRef); + constructor(typeRef: TypeRef, valuesRef: TypeRef | undefined, transformation: Transformation | undefined) { + super(typeRef, "map", false, OrderedMap(), valuesRef, transformation); } // FIXME: Remove and use `getAdditionalProperties()` instead. @@ -535,12 +548,8 @@ export class EnumType extends Type { // @ts-ignore: This is initialized in the Type constructor kind: "enum"; - constructor(typeRef: TypeRef, readonly cases: OrderedSet) { - super(typeRef, "enum"); - } - - get children(): OrderedSet { - return OrderedSet(); + constructor(typeRef: TypeRef, readonly cases: OrderedSet, transformation: Transformation | undefined) { + super(typeRef, "enum", transformation); } get isNullable(): boolean { @@ -552,11 +561,11 @@ export class EnumType extends Type { } reconstitute(builder: TypeReconstituter): void { - builder.getEnumType(this.cases); + builder.getEnumType(this.cases, this.reconstituteTransformation(builder)); } - protected structuralEqualityStep(other: EnumType, _queue: (a: Type, b: Type) => void): boolean { - return this.cases.toSet().equals(other.cases.toSet()); + protected structuralEqualityStep(other: EnumType, queue: (a: Type, b: Type) => boolean): boolean { + return super.structuralEqualityStep(other, queue) && this.cases.toSet().equals(other.cases.toSet()); } } @@ -578,15 +587,24 @@ export function setOperationCasesEqual( } export abstract class SetOperationType extends Type { - constructor(typeRef: TypeRef, kind: TypeKind, private _memberRefs?: OrderedSet) { - super(typeRef, kind); + constructor( + typeRef: TypeRef, + kind: TypeKind, + private _memberRefs: OrderedSet | undefined, + transformation: Transformation | undefined + ) { + super(typeRef, kind, transformation); + assert(transformation === undefined, "We don't support set operations with transformations yet"); } - setMembers(memberRefs: OrderedSet): void { + setMembers(memberRefs: OrderedSet, transformation: Transformation | undefined): void { if (this._memberRefs !== undefined) { return panic("Can only set map members once"); } this._memberRefs = memberRefs; + + assert(transformation === undefined, "We don't support set operations with transformations yet"); + this.setTransformation(transformation); } protected getMemberRefs(): OrderedSet { @@ -605,8 +623,8 @@ export abstract class SetOperationType extends Type { return this.members.sortBy(t => t.kind); } - get children(): OrderedSet { - return this.sortedMembers; + getChildren(): OrderedSet { + return super.getChildren().union(this.sortedMembers); } isPrimitive(): this is PrimitiveType { @@ -614,7 +632,7 @@ export abstract class SetOperationType extends Type { } protected structuralEqualityStep(other: SetOperationType, queue: (a: Type, b: Type) => boolean): boolean { - return setOperationCasesEqual(this.members, other.members, queue); + return super.structuralEqualityStep(other, queue) && setOperationCasesEqual(this.members, other.members, queue); } } @@ -622,8 +640,8 @@ export class IntersectionType extends SetOperationType { // @ts-ignore: This is initialized in the Type constructor kind: "intersection"; - constructor(typeRef: TypeRef, memberRefs?: OrderedSet) { - super(typeRef, "intersection", memberRefs); + constructor(typeRef: TypeRef, memberRefs: OrderedSet | undefined, transformation: Transformation | undefined) { + super(typeRef, "intersection", memberRefs, transformation); } get isNullable(): boolean { @@ -635,9 +653,9 @@ export class IntersectionType extends SetOperationType { const maybeMembers = builder.lookup(memberRefs); if (maybeMembers === undefined) { builder.getUniqueIntersectionType(); - builder.setSetOperationMembers(builder.reconstitute(memberRefs)); + builder.setSetOperationMembers(builder.reconstitute(memberRefs), this.reconstituteTransformation(builder)); } else { - builder.getIntersectionType(maybeMembers); + builder.getIntersectionType(maybeMembers, this.reconstituteTransformation(builder)); } } } @@ -646,16 +664,16 @@ export class UnionType extends SetOperationType { // @ts-ignore: This is initialized in the Type constructor kind: "union"; - constructor(typeRef: TypeRef, memberRefs?: OrderedSet) { - super(typeRef, "union", memberRefs); + constructor(typeRef: TypeRef, memberRefs: OrderedSet | undefined, transformation: Transformation | undefined) { + super(typeRef, "union", memberRefs, transformation); if (memberRefs !== undefined) { messageAssert(!memberRefs.isEmpty(), ErrorMessage.IRNoEmptyUnions); } } - setMembers(memberRefs: OrderedSet): void { + setMembers(memberRefs: OrderedSet, transformation: Transformation | undefined): void { messageAssert(!memberRefs.isEmpty(), ErrorMessage.IRNoEmptyUnions); - super.setMembers(memberRefs); + super.setMembers(memberRefs, transformation); } get stringTypeMembers(): OrderedSet { @@ -693,9 +711,9 @@ export class UnionType extends SetOperationType { const maybeMembers = builder.lookup(memberRefs); if (maybeMembers === undefined) { builder.getUniqueUnionType(); - builder.setSetOperationMembers(builder.reconstitute(memberRefs)); + builder.setSetOperationMembers(builder.reconstitute(memberRefs), this.reconstituteTransformation(builder)); } else { - builder.getUnionType(maybeMembers); + builder.getUnionType(maybeMembers, this.reconstituteTransformation(builder)); } } } diff --git a/src/TypeBuilder.ts b/src/TypeBuilder.ts index 37b11201..be07b574 100644 --- a/src/TypeBuilder.ts +++ b/src/TypeBuilder.ts @@ -1,6 +1,6 @@ "use strict"; -import { Map, OrderedMap, OrderedSet, Set, List } from "immutable"; +import { Map, OrderedMap, OrderedSet, Set } from "immutable"; import { PrimitiveTypeKind, @@ -16,13 +16,12 @@ import { ClassProperty, IntersectionType, ObjectType, - Transformer, Transformation } from "./Type"; import { removeNullFromUnion } from "./TypeUtils"; import { TypeGraph } from "./TypeGraph"; import { TypeAttributes, combineTypeAttributes, TypeAttributeKind } from "./TypeAttributes"; -import { defined, assert, panic, setUnion, mapOptional } from "./Support"; +import { defined, assert, panic, setUnion, mapOptional, ifUndefined } from "./Support"; export class TypeRef { constructor(readonly graph: TypeGraph, readonly index: number) {} @@ -179,7 +178,7 @@ export class TypeBuilder { protected addForwardingIntersection(forwardingRef: TypeRef, tref: TypeRef): TypeRef { this._addedForwardingIntersection = true; - return this.addType(forwardingRef, tr => new IntersectionType(tr, OrderedSet([tref])), undefined); + return this.addType(forwardingRef, tr => new IntersectionType(tr, OrderedSet([tref]), undefined), undefined); } protected forwardIfNecessary(forwardingRef: TypeRef | undefined, tref: undefined): undefined; @@ -205,20 +204,21 @@ export class TypeBuilder { private _classTypes: Map, TypeRef> = Map(); private _unionTypes: Map, TypeRef> = Map(); private _intersectionTypes: Map, TypeRef> = Map(); - private _transformedTypes: Map, TypeRef> = Map(); - getPrimitiveType(kind: PrimitiveTypeKind, forwardingRef?: TypeRef): TypeRef { + getPrimitiveType(kind: PrimitiveTypeKind, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef { assert(kind !== "string", "Use getStringType to create strings"); if (kind === "date") kind = this._stringTypeMapping.date; if (kind === "time") kind = this._stringTypeMapping.time; if (kind === "date-time") kind = this._stringTypeMapping.dateTime; if (kind === "string") { - return this.getStringType(undefined, undefined, forwardingRef); + return this.getStringType(undefined, undefined, transformation, forwardingRef); } - let tref = this.forwardIfNecessary(forwardingRef, this._primitiveTypes.get(kind)); + let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._primitiveTypes.get(kind))); if (tref === undefined) { - tref = this.addType(forwardingRef, tr => new PrimitiveType(tr, kind), undefined); - this._primitiveTypes = this._primitiveTypes.set(kind, tref); + tref = this.addType(forwardingRef, tr => new PrimitiveType(tr, kind, transformation), undefined); + if (transformation === undefined) { + this._primitiveTypes = this._primitiveTypes.set(kind, tref); + } } return tref; } @@ -226,6 +226,7 @@ export class TypeBuilder { getStringType( attributes: TypeAttributes | undefined, cases: OrderedMap | undefined, + transformation?: Transformation, forwardingRef?: TypeRef ): TypeRef { if (cases === undefined) { @@ -240,27 +241,32 @@ export class TypeBuilder { // 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) { - result = this._noEnumStringType = this.addType( + if (this._noEnumStringType === undefined || transformation !== undefined) { + result = this.addType( forwardingRef, - tr => new StringType(tr, undefined), + tr => new StringType(tr, undefined, transformation), undefined ); + if (transformation === undefined) { + this._noEnumStringType = result; + } } else { result = this.forwardIfNecessary(forwardingRef, this._noEnumStringType); } - this.addAttributes(this._noEnumStringType, attributes); + this.addAttributes(result, attributes); return result; } - return this.addType(forwardingRef, tr => new StringType(tr, cases), attributes); + return this.addType(forwardingRef, tr => new StringType(tr, cases, transformation), attributes); } - getEnumType(attributes: TypeAttributes, cases: OrderedSet, forwardingRef?: TypeRef): TypeRef { + getEnumType(attributes: TypeAttributes, cases: OrderedSet, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef { const unorderedCases = cases.toSet(); - let tref = this.forwardIfNecessary(forwardingRef, this._enumTypes.get(unorderedCases)); + let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._enumTypes.get(unorderedCases))); if (tref === undefined) { - tref = this.addType(forwardingRef, tr => new EnumType(tr, cases), attributes); - this._enumTypes = this._enumTypes.set(unorderedCases, tref); + 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); } @@ -271,18 +277,19 @@ export class TypeBuilder { attributes: TypeAttributes, properties: OrderedMap | undefined, additionalProperties: TypeRef | undefined, + transformation?: Transformation, forwardingRef?: TypeRef ): TypeRef { properties = mapOptional(p => this.modifyPropertiesIfNecessary(p), properties); return this.addType( forwardingRef, - tref => new ObjectType(tref, "object", true, properties, additionalProperties), + tref => new ObjectType(tref, "object", true, properties, additionalProperties, transformation), attributes ); } getUniqueMapType(forwardingRef?: TypeRef): TypeRef { - return this.addType(forwardingRef, tr => new MapType(tr, undefined), undefined); + return this.addType(forwardingRef, tr => new MapType(tr, undefined, undefined), undefined); } private registerMapType(values: TypeRef, tref: TypeRef): void { @@ -290,11 +297,13 @@ export class TypeBuilder { this._mapTypes = this._mapTypes.set(values, tref); } - getMapType(values: TypeRef, forwardingRef?: TypeRef): TypeRef { - let tref = this.forwardIfNecessary(forwardingRef, this._mapTypes.get(values)); + 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), undefined); - this.registerMapType(values, tref); + tref = this.addType(forwardingRef, tr => new MapType(tr, values, transformation), undefined); + if (transformation === undefined) { + this.registerMapType(values, tref); + } } return tref; } @@ -307,13 +316,16 @@ export class TypeBuilder { setObjectProperties( ref: TypeRef, properties: OrderedMap, - additionalProperties: TypeRef | undefined + additionalProperties: TypeRef | undefined, + transformation?: Transformation ): void { const type = ref.deref()[0]; if (!(type instanceof ObjectType)) { return panic("Tried to set properties of non-object type"); } - type.setProperties(this.modifyPropertiesIfNecessary(properties), additionalProperties); + 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) { @@ -324,7 +336,7 @@ export class TypeBuilder { } getUniqueArrayType(forwardingRef?: TypeRef): TypeRef { - return this.addType(forwardingRef, tr => new ArrayType(tr, undefined), undefined); + return this.addType(forwardingRef, tr => new ArrayType(tr, undefined, undefined), undefined); } private registerArrayType(items: TypeRef, tref: TypeRef): void { @@ -332,22 +344,26 @@ export class TypeBuilder { this._arrayTypes = this._arrayTypes.set(items, tref); } - getArrayType(items: TypeRef, forwardingRef?: TypeRef): TypeRef { - let tref = this.forwardIfNecessary(forwardingRef, this._arrayTypes.get(items)); + 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), undefined); - this.registerArrayType(items, tref); + tref = this.addType(forwardingRef, tr => new ArrayType(tr, items, transformation), undefined); + if (transformation === undefined) { + this.registerArrayType(items, tref); + } } return tref; } - setArrayItems(ref: TypeRef, items: TypeRef): void { + setArrayItems(ref: TypeRef, items: TypeRef, transformation?: Transformation): void { const type = ref.deref()[0]; if (!(type instanceof ArrayType)) { return panic("Tried to set items of non-array type"); } - type.setItems(items); - this.registerArrayType(items, ref); + type.setItems(items, transformation); + if (transformation === undefined) { + this.registerArrayType(items, ref); + } } modifyPropertiesIfNecessary(properties: OrderedMap): OrderedMap { @@ -363,13 +379,16 @@ export class TypeBuilder { getClassType( attributes: TypeAttributes, properties: OrderedMap, + transformation?: Transformation, forwardingRef?: TypeRef ): TypeRef { properties = this.modifyPropertiesIfNecessary(properties); - let tref = this.forwardIfNecessary(forwardingRef, this._classTypes.get(properties.toMap())); + 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), attributes); - this.registerClassType(properties, tref); + tref = this.addType(forwardingRef, tr => new ClassType(tr, false, properties, transformation), attributes); + if (transformation === undefined) { + this.registerClassType(properties, tref); + } } else { this.addAttributes(tref, attributes); } @@ -387,18 +406,21 @@ export class TypeBuilder { attributes: TypeAttributes, isFixed: boolean, properties: OrderedMap | undefined, + transformation?: Transformation, forwardingRef?: TypeRef ): TypeRef { properties = mapOptional(p => this.modifyPropertiesIfNecessary(p), properties); - return this.addType(forwardingRef, tref => new ClassType(tref, isFixed, properties), attributes); + return this.addType(forwardingRef, tref => new ClassType(tref, isFixed, properties, transformation), attributes); } - getUnionType(attributes: TypeAttributes, members: OrderedSet, forwardingRef?: TypeRef): TypeRef { + getUnionType(attributes: TypeAttributes, members: OrderedSet, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef { const unorderedMembers = members.toSet(); - let tref = this.forwardIfNecessary(forwardingRef, this._unionTypes.get(unorderedMembers)); + let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._unionTypes.get(unorderedMembers))); if (tref === undefined) { - tref = this.addType(forwardingRef, tr => new UnionType(tr, members), attributes); - this.registerUnionType(unorderedMembers, tref); + tref = this.addType(forwardingRef, tr => new UnionType(tr, members, transformation), attributes); + if (transformation === undefined) { + this.registerUnionType(unorderedMembers, tref); + } } else { this.addAttributes(tref, attributes); } @@ -408,9 +430,10 @@ export class TypeBuilder { getUniqueUnionType( attributes: TypeAttributes, members: OrderedSet | undefined, + transformation?: Transformation, forwardingRef?: TypeRef ): TypeRef { - return this.addType(forwardingRef, tref => new UnionType(tref, members), attributes); + return this.addType(forwardingRef, tref => new UnionType(tref, members, transformation), attributes); } private registerIntersectionType(members: Set, tref: TypeRef): void { @@ -418,12 +441,14 @@ export class TypeBuilder { this._intersectionTypes = this._intersectionTypes.set(members, tref); } - getIntersectionType(attributes: TypeAttributes, members: OrderedSet, forwardingRef?: TypeRef): TypeRef { + getIntersectionType(attributes: TypeAttributes, members: OrderedSet, transformation?: Transformation, forwardingRef?: TypeRef): TypeRef { const unorderedMembers = members.toSet(); - let tref = this.forwardIfNecessary(forwardingRef, this._intersectionTypes.get(unorderedMembers)); + let tref = ifUndefined(transformation, () => this.forwardIfNecessary(forwardingRef, this._intersectionTypes.get(unorderedMembers))); if (tref === undefined) { - tref = this.addType(forwardingRef, tr => new IntersectionType(tr, members), attributes); - this.registerIntersectionType(unorderedMembers, tref); + tref = this.addType(forwardingRef, tr => new IntersectionType(tr, members, transformation), attributes); + if (transformation === undefined) { + this.registerIntersectionType(unorderedMembers, tref); + } } else { this.addAttributes(tref, attributes); } @@ -433,17 +458,18 @@ export class TypeBuilder { getUniqueIntersectionType( attributes: TypeAttributes, members: OrderedSet | undefined, + transformation?: Transformation, forwardingRef?: TypeRef ): TypeRef { - return this.addType(forwardingRef, tref => new IntersectionType(tref, members), attributes); + return this.addType(forwardingRef, tref => new IntersectionType(tref, members, transformation), attributes); } - setSetOperationMembers(ref: TypeRef, members: OrderedSet): void { + setSetOperationMembers(ref: TypeRef, members: OrderedSet, transformation?: Transformation): void { const type = ref.deref()[0]; if (!(type instanceof UnionType || type instanceof IntersectionType)) { return panic("Tried to set members of non-set-operation type"); } - type.setMembers(members); + type.setMembers(members, transformation); const unorderedMembers = members.toSet(); if (type instanceof UnionType) { this.registerUnionType(unorderedMembers, ref); @@ -454,59 +480,7 @@ export class TypeBuilder { } } - setTransformation(ref: TypeRef, transformation: Transformation): void { - const type = ref.deref()[0]; - type.setTransformation(transformation); - } - setLostTypeAttributes(): void { return; } - - private registerTransformedType( - transformer: Transformer, - sourceRef: TypeRef, - targetRef: TypeRef, - tref: TypeRef - ): void { - const key = List([transformer, sourceRef, targetRef]); - if (this._transformedTypes.has(key)) return; - this._transformedTypes = this._transformedTypes.set(key, tref); - } - - getTransformedType( - attributes: TypeAttributes, - transformer: Transformer, - sourceRef: TypeRef, - targetRef: TypeRef, - forwardingRef?: TypeRef - ): TypeRef { - const key = List([transformer, sourceRef, targetRef]); - let tref = this.forwardIfNecessary(forwardingRef, this._transformedTypes.get(key)); - if (tref === undefined) { - tref = this.addType( - forwardingRef, - tr => new TransformedType(tr, transformer, sourceRef, targetRef), - attributes - ); - this.registerTransformedType(transformer, sourceRef, targetRef, tref); - } else { - this.addAttributes(tref, attributes); - } - return tref; - } - - getUniqueTransformedType(attributes: TypeAttributes, transformer: Transformer, forwardingRef?: TypeRef): TypeRef { - return this.addType(forwardingRef, tref => new TransformedType(tref, transformer), attributes); - } - - setTransformedTypeTypes(ref: TypeRef, sourceRef: TypeRef, targetRef: TypeRef): void { - const type = ref.deref()[0]; - if (!(type instanceof TransformedType)) { - return panic("Tried to set types of non-transformed type"); - } - - type.setTypes(sourceRef, targetRef); - this.registerTransformedType(type.transformer, sourceRef, targetRef, ref); - } } diff --git a/src/TypeGraph.ts b/src/TypeGraph.ts index 28fc1bcc..0c8d9a56 100644 --- a/src/TypeGraph.ts +++ b/src/TypeGraph.ts @@ -184,7 +184,7 @@ export class TypeGraph { types = types.push(t); } - const children = childrenOfType !== undefined ? childrenOfType(t) : t.children; + const children = childrenOfType !== undefined ? childrenOfType(t) : t.getChildren(); children.forEach(addFromType); if (!topDown && required) { @@ -345,7 +345,7 @@ export class TypeGraph { if (this._parents === undefined) { const parents = defined(this._types).map(_ => Set()); this.allTypesUnordered().forEach(p => { - p.children.forEach(c => { + p.getChildren().forEach(c => { const index = c.typeRef.index; parents[index] = parents[index].add(p); }); @@ -361,7 +361,7 @@ export class TypeGraph { const t = types[i]; const parts: string[] = []; parts.push(`${t.debugPrintKind}${t.hasNames ? ` ${t.getCombinedName()}` : ""}`); - const children = t.children; + const children = t.getChildren(); if (!children.isEmpty()) { parts.push(`children ${children.map(c => c.typeRef.index).join(",")}`); } @@ -385,6 +385,7 @@ export function noneToAny( if (noneTypes.size === 0) { return graph; } + assert(noneTypes.every(t => t.transformation === undefined), "We don't support none types with transformations"); assert(noneTypes.size === 1, "Cannot have more than one none type"); return graph.rewrite( "none to any", @@ -393,7 +394,7 @@ export function noneToAny( [noneTypes.toArray()], debugPrintReconstitution, (types, builder, forwardingRef) => { - const tref = builder.getPrimitiveType("any", forwardingRef); + const tref = builder.getPrimitiveType("any", undefined, forwardingRef); const attributes = combineTypeAttributesOfTypes(types); builder.addAttributes(tref, attributes); return tref; @@ -428,15 +429,15 @@ export function optionalToNullable( return new ClassProperty(ref, false); }); if (c.isFixed) { - return builder.getUniqueClassType(c.getAttributes(), true, properties, forwardingRef); + return builder.getUniqueClassType(c.getAttributes(), true, properties, undefined, forwardingRef); } else { - return builder.getClassType(c.getAttributes(), properties, forwardingRef); + return builder.getClassType(c.getAttributes(), properties, undefined, forwardingRef); } } const classesWithOptional = graph .allTypesUnordered() - .filter(t => t instanceof ClassType && t.getProperties().some(p => p.isOptional)); + .filter(t => t instanceof ClassType && t.transformation === undefined && t.getProperties().some(p => p.isOptional)); const replacementGroups = classesWithOptional.map(c => [c as ClassType]).toArray(); if (classesWithOptional.size === 0) { return graph; diff --git a/src/TypeUtils.ts b/src/TypeUtils.ts index ab00e63f..1881f09b 100644 --- a/src/TypeUtils.ts +++ b/src/TypeUtils.ts @@ -15,8 +15,7 @@ import { ClassType, ClassProperty, SetOperationType, - UnionType, - TransformedType + UnionType } from "./Type"; export function assertIsObject(t: Type): ObjectType { @@ -213,7 +212,6 @@ export function matchTypeExhaustive( objectType: (objectType: ObjectType) => U, enumType: (enumType: EnumType) => U, unionType: (unionType: UnionType) => U, - transformedType: (transformedType: TransformedType) => U, dateType: (dateType: PrimitiveType) => U, timeType: (timeType: PrimitiveType) => U, dateTimeType: (dateTimeType: PrimitiveType) => U @@ -239,7 +237,6 @@ export function matchTypeExhaustive( else if (t instanceof ObjectType) return objectType(t); else if (t instanceof EnumType) return enumType(t); else if (t instanceof UnionType) return unionType(t); - else if (t instanceof TransformedType) return transformedType(t); return panic(`Unknown type ${t.kind}`); } @@ -281,7 +278,6 @@ export function matchType( typeNotSupported, enumType, unionType, - typeNotSupported, stringTypeMatchers.dateType || typeNotSupported, stringTypeMatchers.timeType || typeNotSupported, stringTypeMatchers.dateTimeType || typeNotSupported @@ -295,8 +291,7 @@ export function matchCompoundType( classType: (classType: ClassType) => void, mapType: (mapType: MapType) => void, objectType: (objectType: ObjectType) => void, - unionType: (unionType: UnionType) => void, - transformedType: (transformedType: TransformedType) => void + unionType: (unionType: UnionType) => void ): void { function ignore(_: T): void { return; @@ -317,7 +312,6 @@ export function matchCompoundType( objectType, ignore, unionType, - transformedType, ignore, ignore, ignore diff --git a/src/UnifyClasses.ts b/src/UnifyClasses.ts index e3742deb..a03e0989 100644 --- a/src/UnifyClasses.ts +++ b/src/UnifyClasses.ts @@ -112,9 +112,9 @@ export class UnifyUnionBuilder extends UnionBuilder( conflateNumbers: boolean, maybeForwardingRef?: TypeRef ): TypeRef { + assert(types.every(t => t.transformation === undefined), "We don't support unifying types with transformations yet"); if (types.isEmpty()) { return panic("Cannot unify empty set of types"); } else if (types.count() === 1) { diff --git a/src/UnionBuilder.ts b/src/UnionBuilder.ts index 07ecc5b4..2f78151b 100644 --- a/src/UnionBuilder.ts +++ b/src/UnionBuilder.ts @@ -233,6 +233,7 @@ export class TypeRefUnionAccumulator extends UnionAccumulator // There is a method analogous to this in the IntersectionAccumulator. It might // make sense to find a common interface. private addType(t: Type, attributes: TypeAttributes): void { + assert(t.transformation === undefined, "We don't support transformations in unions yet"); matchTypeExhaustive( t, _noneType => this.addNone(attributes), @@ -260,9 +261,6 @@ export class TypeRefUnionAccumulator extends UnionAccumulator _unionType => { return panic("The unions should have been eliminated in attributesForTypesInUnion"); }, - _transformedType => { - return panic("We don't support transformed types in unions yet"); - }, _dateType => this.addStringType("date", attributes), _timeType => this.addStringType("time", attributes), _dateTimeType => this.addStringType("date-time", attributes) @@ -312,11 +310,11 @@ export abstract class UnionBuilder