We now always construct a "unique" type before reconstituting the
original type's children.  That way we can't get into direct loops
anymore that we need to avoid with obscure machinery.
This commit is contained in:
Mark Probst 2018-03-30 08:01:52 -07:00
Родитель 50471d767a
Коммит 18b17ad01b
22 изменённых файлов: 531 добавлений и 196 удалений

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

@ -50,7 +50,7 @@ function lookupKey(accessors: AccessorNames, key: string, language: string): [st
export function objectPropertyNames(o: ObjectType, language: string): Map<string, [string, boolean] | undefined> {
const accessors = accessorNamesTypeAttributeKind.tryGetInAttributes(o.getAttributes());
const map = o.properties;
const map = o.getProperties();
if (accessors === undefined) return map.map(_ => undefined);
return map.map((_cp, n) => lookupKey(accessors, n, language));
}

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

@ -31,8 +31,8 @@ function typeSetsCanBeCombined(s1: OrderedSet<Type>, s2: OrderedSet<Type>): bool
}
function canBeCombined(c1: ClassType, c2: ClassType): boolean {
const p1 = c1.properties;
const p2 = c2.properties;
const p1 = c1.getProperties();
const p2 = c2.getProperties();
if (p1.size < p2.size * REQUIRED_OVERLAP || p2.size < p1.size * REQUIRED_OVERLAP) {
return false;
}
@ -127,7 +127,8 @@ export function combineClasses(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
alphabetizeProperties: boolean,
conflateNumbers: boolean
conflateNumbers: boolean,
debugPrintReconstitution: boolean
): TypeGraph {
const cliques = findSimilarityCliques(graph, false);
@ -148,5 +149,12 @@ export function combineClasses(
);
}
return graph.rewrite("combine classes", stringTypeMapping, alphabetizeProperties, cliques, makeCliqueClass);
return graph.rewrite(
"combine classes",
stringTypeMapping,
alphabetizeProperties,
cliques,
debugPrintReconstitution,
makeCliqueClass
);
}

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

@ -347,7 +347,8 @@ export abstract class ConvenienceRenderer extends Renderer {
let ns: Namespace | undefined;
const accessorNames = objectPropertyNames(o, this.targetLanguage.name);
const names = o.sortedProperties
const names = o
.getSortedProperties()
.map((p, jsonName) => {
const [assignedName, isFixed] = getAccessorName(accessorNames, jsonName);
let name: Name | undefined;
@ -452,7 +453,8 @@ export abstract class ConvenienceRenderer extends Renderer {
const names = this.names;
if (t instanceof ClassType) {
const propertyNameds = defined(this._propertyNamesStoreView).get(t);
const sortedMap = t.properties
const sortedMap = t
.getProperties()
.filter((_, n) => propertyNameds.get(n) !== undefined)
.map(p => p.type)
.sortBy((_, n) => defined(names.get(defined(propertyNameds.get(n)))));
@ -602,11 +604,11 @@ export abstract class ConvenienceRenderer extends Renderer {
if (this._alphabetizeProperties) {
const alphabetizedPropertyNames = propertyNames.sortBy(n => this.names.get(n)).toOrderedMap();
this.forEachWithBlankLines(alphabetizedPropertyNames, blankLocations, (name, jsonName) => {
const p = defined(o.properties.get(jsonName));
const p = defined(o.getProperties().get(jsonName));
f(name, jsonName, p);
});
} else {
this.forEachWithBlankLines(o.properties, blankLocations, (p, jsonName) => {
this.forEachWithBlankLines(o.getProperties(), blankLocations, (p, jsonName) => {
const name = defined(propertyNames.get(jsonName));
f(name, jsonName, p);
});
@ -806,7 +808,7 @@ export abstract class ConvenienceRenderer extends Renderer {
this._haveMaps = types.some(t => t instanceof MapType);
this._haveOptionalProperties = types
.filter(t => t instanceof ClassType)
.some(c => (c as ClassType).properties.some(p => p.isOptional));
.some(c => (c as ClassType).getProperties().some(p => p.isOptional));
this._namedTypes = this._declarationIR.declarations.filter(d => d.kind === "define").map(d => d.type);
const { objects, enums, unions } = separateNamedTypes(this._namedTypes);
this._namedObjects = objects;

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

@ -13,7 +13,8 @@ export function flattenUnions(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
conflateNumbers: boolean,
makeObjectTypes: boolean
makeObjectTypes: boolean,
debugPrintReconstitution: boolean
): [TypeGraph, boolean] {
let needsRepeat = false;
@ -47,7 +48,7 @@ export function flattenUnions(
foundIntersection = true;
return false;
});
graph = graph.rewrite("flatten unions", stringTypeMapping, false, groups, replace);
graph = graph.rewrite("flatten unions", stringTypeMapping, false, groups, debugPrintReconstitution, replace);
// console.log(`flattened ${nonCanonicalUnions.size} of ${unions.size} unions`);
return [graph, !needsRepeat && !foundIntersection];

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

@ -21,12 +21,12 @@ export function gatherNames(graph: TypeGraph): void {
let processed: Set<List<any>> = Set();
function processObjectType(o: ObjectType, names: OrderedSet<string>, parentNames: OrderedSet<string> | undefined) {
const properties = o.properties.sortBy((_, n) => n);
const properties = o.getProperties().sortBy((_, n) => n);
properties.forEach((property, propertyName) => {
processType(property.type, OrderedSet([propertyName]), names);
});
const values = o.additionalProperties;
const values = o.getAdditionalProperties();
if (values !== undefined) {
processType(values, names.map(pluralize.singular), parentNames, "value");
}

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

@ -68,20 +68,35 @@ function replaceUnion(group: Set<UnionType>, builder: GraphRewriteBuilder<UnionT
return builder.getUnionType(u.getAttributes(), OrderedSet(types), forwardingRef);
}
export function inferEnums(graph: TypeGraph, stringTypeMapping: StringTypeMapping): TypeGraph {
export function inferEnums(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
debugPrintReconstitution: boolean
): TypeGraph {
const allStrings = graph
.allTypesUnordered()
.filter(t => t instanceof StringType)
.map(t => [t])
.toArray() as StringType[][];
return graph.rewrite("infer enums", stringTypeMapping, false, allStrings, replaceString);
return graph.rewrite("infer enums", stringTypeMapping, false, allStrings, debugPrintReconstitution, replaceString);
}
export function flattenStrings(graph: TypeGraph, stringTypeMapping: StringTypeMapping): TypeGraph {
export function flattenStrings(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
debugPrintReconstitution: boolean
): TypeGraph {
const allUnions = graph.allNamedTypesSeparated().unions;
const unionsToReplace = allUnions
.filter(unionNeedsReplacing)
.map(t => [t])
.toArray();
return graph.rewrite("flatten strings", stringTypeMapping, false, unionsToReplace, replaceUnion);
return graph.rewrite(
"flatten strings",
stringTypeMapping,
false,
unionsToReplace,
debugPrintReconstitution,
replaceUnion
);
}

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

@ -86,14 +86,19 @@ function shouldBeMap(properties: Map<string, ClassProperty>): Set<Type> | undefi
return allCases;
}
export function inferMaps(graph: TypeGraph, stringTypeMapping: StringTypeMapping, conflateNumbers: boolean): TypeGraph {
export function inferMaps(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
conflateNumbers: boolean,
debugPrintReconstitution: boolean
): TypeGraph {
function replaceClass(
setOfOneClass: Set<ClassType>,
builder: GraphRewriteBuilder<ClassType>,
forwardingRef: TypeRef
): TypeRef {
const c = defined(setOfOneClass.first());
const properties = c.properties;
const properties = c.getProperties();
const shouldBe = shouldBeMap(properties);
if (shouldBe === undefined) {
@ -123,6 +128,15 @@ export function inferMaps(graph: TypeGraph, stringTypeMapping: StringTypeMapping
const allClasses = graph.allNamedTypesSeparated().objects.filter(o => o instanceof ClassType) as OrderedSet<
ClassType
>;
const classesToReplace = allClasses.filter(c => !c.isFixed && shouldBeMap(c.properties) !== undefined).toArray();
return graph.rewrite("infer maps", stringTypeMapping, false, classesToReplace.map(c => [c]), replaceClass);
const classesToReplace = allClasses
.filter(c => !c.isFixed && shouldBeMap(c.getProperties()) !== undefined)
.toArray();
return graph.rewrite(
"infer maps",
stringTypeMapping,
false,
classesToReplace.map(c => [c]),
debugPrintReconstitution,
replaceClass
);
}

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

@ -331,7 +331,7 @@ export class CSharpRenderer extends ConvenienceRenderer {
className,
this.superclassForType(c),
() => {
if (c.properties.isEmpty()) return;
if (c.getProperties().isEmpty()) return;
const blankLines = this.blankLinesBetweenAttributes() ? "interposing" : "none";
let columns: Sourcelike[][] = [];
let isFirstProperty = true;

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

@ -136,13 +136,13 @@ export class JSONSchemaRenderer extends ConvenienceRenderer {
private definitionForObject(o: ObjectType, title: string | undefined): Schema {
let properties: Schema | undefined;
let required: string[] | undefined;
if (o.properties.isEmpty()) {
if (o.getProperties().isEmpty()) {
properties = undefined;
required = undefined;
} else {
const props: Schema = {};
const req: string[] = [];
o.properties.forEach((p, name) => {
o.getProperties().forEach((p, name) => {
props[name] = this.schemaForType(p.type);
if (!p.isOptional) {
req.push(name);
@ -151,8 +151,8 @@ export class JSONSchemaRenderer extends ConvenienceRenderer {
properties = props;
required = req.sort();
}
const additionalProperties =
o.additionalProperties !== undefined ? this.schemaForType(o.additionalProperties) : false;
const additional = o.getAdditionalProperties();
const additionalProperties = additional !== undefined ? this.schemaForType(additional) : false;
return {
type: "object",
additionalProperties,

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

@ -356,7 +356,7 @@ export class JavaRenderer extends ConvenienceRenderer {
}
protected emitClassAttributes(c: ClassType, _className: Name): void {
if (c.properties.isEmpty() && !this._justTypes) {
if (c.getProperties().isEmpty() && !this._justTypes) {
this.emitLine("@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.NONE)");
}
}

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

@ -187,8 +187,9 @@ export class JavaScriptRenderer extends ConvenienceRenderer {
this.emitBlock(`const typeMap${anyAnnotation} = `, ";", () => {
this.forEachObject("none", (t: ObjectType, name: Name) => {
const additionalProperties = t.getAdditionalProperties();
const additional =
t.additionalProperties !== undefined ? this.typeMapTypeFor(t.additionalProperties) : "false";
additionalProperties !== undefined ? this.typeMapTypeFor(additionalProperties) : "false";
this.emitBlock(['"', name, '": o('], [", ", additional, "),"], () => {
this.forEachClassProperty(t, "none", (propName, _propJsonName, property) => {
this.emitLine(propName, ": ", this.typeMapTypeForProperty(property), ",");

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

@ -668,7 +668,7 @@ export class ObjectiveCRenderer extends ConvenienceRenderer {
});
if (!this._justTypes && isTopLevel) {
if (t.properties.count() > 0) this.ensureBlankLine();
if (!t.getProperties().isEmpty()) this.ensureBlankLine();
this.emitLine(
"+ (_Nullable instancetype)fromJSON:(NSString *)json encoding:(NSStringEncoding)encoding error:(NSError *_Nullable *)error;"
@ -1048,7 +1048,7 @@ export class ObjectiveCRenderer extends ConvenienceRenderer {
return (
t instanceof MapType ||
t instanceof ArrayType ||
(t instanceof ClassType && t.properties.some(p => needsMap(p.type)))
(t instanceof ClassType && t.getProperties().some(p => needsMap(p.type)))
);
}
return this.typeGraph.allTypesUnordered().some(needsMap);

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

@ -401,7 +401,7 @@ export class RubyRenderer extends ConvenienceRenderer {
this.emitDescription(this.descriptionForType(c));
this.emitBlock(["class ", className, " < Dry::Struct"], () => {
let table: Sourcelike[][] = [];
let count = c.properties.count();
let count = c.getProperties().size;
this.forEachClassProperty(c, "none", (name, jsonName, p) => {
const last = --count === 0;
const description = this.descriptionForClassProperty(c, jsonName);

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

@ -534,7 +534,7 @@ export class SwiftRenderer extends ConvenienceRenderer {
const allPropertiesRedundant = groups.every(group => {
return group.every(p => p.label === undefined);
});
if (!allPropertiesRedundant && !c.properties.isEmpty()) {
if (!allPropertiesRedundant && !c.getProperties().isEmpty()) {
this.ensureBlankLine();
this.emitBlock("enum CodingKeys: String, CodingKey", () => {
for (const group of groups) {

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

@ -12,7 +12,8 @@ export function replaceObjectType(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
_conflateNumbers: boolean,
leaveFullObjects: boolean
leaveFullObjects: boolean,
debugPrintReconstitution: boolean
): TypeGraph {
function replace(
setOfOneType: Set<ObjectType>,
@ -21,8 +22,8 @@ export function replaceObjectType(
): TypeRef {
const o = defined(setOfOneType.first());
const attributes = o.getAttributes();
const properties = o.properties;
const additionalProperties = o.additionalProperties;
const properties = o.getProperties();
const additionalProperties = o.getAdditionalProperties();
function reconstituteProperties(): OrderedMap<string, ClassProperty> {
return properties.map(cp => new ClassProperty(builder.reconstituteTypeRef(cp.typeRef), cp.isOptional));
@ -82,8 +83,8 @@ export function replaceObjectType(
const allObjectTypes = graph.allTypesUnordered().filter(t => t.kind === "object") as Set<ObjectType>;
const objectTypesToReplace = leaveFullObjects
? allObjectTypes.filter(o => o.properties.isEmpty() || o.additionalProperties === undefined)
? allObjectTypes.filter(o => o.getProperties().isEmpty() || o.getAdditionalProperties() === undefined)
: allObjectTypes;
const groups = objectTypesToReplace.toArray().map(t => [t]);
return graph.rewrite("replace object type", stringTypeMapping, false, groups, replace);
return graph.rewrite("replace object type", stringTypeMapping, false, groups, debugPrintReconstitution, replace);
}

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

@ -168,6 +168,7 @@ class IntersectionAccumulator
}
this._objectAttributes = combineTypeAttributes(this._objectAttributes, maybeObject.getAttributes());
const objectAdditionalProperties = maybeObject.getAdditionalProperties();
if (this._objectProperties === undefined) {
assert(this._additionalPropertyTypes === undefined);
@ -177,10 +178,10 @@ class IntersectionAccumulator
const allPropertyNames = this._objectProperties
.keySeq()
.toOrderedSet()
.union(maybeObject.properties.keySeq());
.union(maybeObject.getProperties().keySeq());
allPropertyNames.forEach(name => {
const existing = defined(this._objectProperties).get(name);
const newProperty = maybeObject.properties.get(name);
const newProperty = maybeObject.getProperties().get(name);
if (existing !== undefined && newProperty !== undefined) {
const cp = new GenericClassProperty(
@ -188,9 +189,9 @@ class IntersectionAccumulator
existing.isOptional && newProperty.isOptional
);
this._objectProperties = defined(this._objectProperties).set(name, cp);
} else if (existing !== undefined && maybeObject.additionalProperties !== undefined) {
} else if (existing !== undefined && objectAdditionalProperties !== undefined) {
const cp = new GenericClassProperty(
existing.typeData.add(maybeObject.additionalProperties),
existing.typeData.add(objectAdditionalProperties),
existing.isOptional
);
this._objectProperties = defined(this._objectProperties).set(name, cp);
@ -209,9 +210,9 @@ class IntersectionAccumulator
}
});
if (this._additionalPropertyTypes !== undefined && maybeObject.additionalProperties !== undefined) {
this._additionalPropertyTypes = this._additionalPropertyTypes.add(maybeObject.additionalProperties);
} else if (this._additionalPropertyTypes !== undefined || maybeObject.additionalProperties !== undefined) {
if (this._additionalPropertyTypes !== undefined && objectAdditionalProperties !== undefined) {
this._additionalPropertyTypes = this._additionalPropertyTypes.add(objectAdditionalProperties);
} else if (this._additionalPropertyTypes !== undefined || objectAdditionalProperties !== undefined) {
this._additionalPropertyTypes = undefined;
this._lostTypeAttributes = true;
}
@ -404,7 +405,11 @@ class IntersectionUnionBuilder extends UnionBuilder<
}
}
export function resolveIntersections(graph: TypeGraph, stringTypeMapping: StringTypeMapping): [TypeGraph, boolean] {
export function resolveIntersections(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
debugPrintReconstitution: boolean
): [TypeGraph, boolean] {
let needsRepeat = false;
function replace(types: Set<Type>, builder: GraphRewriteBuilder<Type>, forwardingRef: TypeRef): TypeRef {
@ -441,7 +446,7 @@ export function resolveIntersections(graph: TypeGraph, stringTypeMapping: String
>;
const resolvableIntersections = allIntersections.filter(canResolve);
const groups = makeGroupsToFlatten(resolvableIntersections, undefined);
graph = graph.rewrite("resolve intersections", stringTypeMapping, false, groups, replace);
graph = graph.rewrite("resolve intersections", stringTypeMapping, false, groups, debugPrintReconstitution, replace);
// console.log(`resolved ${resolvableIntersections.size} of ${intersections.size} intersections`);
return [graph, !needsRepeat && allIntersections.size === resolvableIntersections.size];

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

@ -62,7 +62,7 @@ export abstract class Type {
abstract get isNullable(): boolean;
abstract isPrimitive(): this is PrimitiveType;
abstract map(builder: TypeReconstituter, f: (tref: TypeRef) => TypeRef): TypeRef;
abstract reconstitute<T extends Type>(builder: TypeReconstituter<T>): void;
equals(other: any): boolean {
if (!Object.prototype.hasOwnProperty.call(other, "typeRef")) {
@ -182,9 +182,8 @@ export class PrimitiveType extends Type {
return true;
}
map(builder: TypeReconstituter, _: (tref: TypeRef) => TypeRef): TypeRef {
// console.log(`${mapIndentation()}mapping ${this.kind}`);
return builder.getPrimitiveType(this.kind);
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
builder.getPrimitiveType(this.kind);
}
protected structuralEqualityStep(_other: Type, _queue: (a: Type, b: Type) => boolean): boolean {
@ -201,9 +200,8 @@ export class StringType extends PrimitiveType {
super(typeRef, "string", false);
}
map(builder: TypeReconstituter, _: (tref: TypeRef) => TypeRef): TypeRef {
// console.log(`${mapIndentation()}mapping ${this.kind}`);
return builder.getStringType(this.enumCases);
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
builder.getStringType(this.enumCases);
}
protected structuralEqualityStep(_other: Type, _queue: (a: Type, b: Type) => boolean): boolean {
@ -249,12 +247,15 @@ export class ArrayType extends Type {
return false;
}
map(builder: TypeReconstituter, f: (tref: TypeRef) => TypeRef): TypeRef {
// console.log(`${mapIndentation()}mapping ${this.kind}`);
// mapPath.push("[]");
const result = builder.getArrayType(f(this.getItemsRef()));
// mapPath.pop();
return result;
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
const itemsRef = this.getItemsRef();
const maybeItems = builder.lookup(itemsRef);
if (maybeItems === undefined) {
builder.getUniqueArrayType();
builder.setArrayItems(builder.reconstitute(this.getItemsRef()));
} else {
builder.getArrayType(maybeItems);
}
}
protected structuralEqualityStep(other: ArrayType, queue: (a: Type, b: Type) => boolean): boolean {
@ -296,14 +297,16 @@ export class ObjectType extends Type {
typeRef: TypeRef,
kind: TypeKind,
readonly isFixed: boolean,
readonly properties: OrderedMap<string, ClassProperty>,
private _properties: OrderedMap<string, ClassProperty> | undefined,
private _additionalPropertiesRef: TypeRef | undefined
) {
super(typeRef, kind);
assert(kind === "object" || kind === "map" || kind === "class");
if (kind === "map") {
assert(properties.isEmpty());
if (_properties !== undefined) {
assert(_properties.isEmpty());
}
assert(!isFixed);
} else if (kind === "class") {
assert(_additionalPropertiesRef === undefined);
@ -312,21 +315,43 @@ export class ObjectType extends Type {
}
}
get sortedProperties(): OrderedMap<string, ClassProperty> {
const properties = this.properties;
setProperties(properties: OrderedMap<string, ClassProperty>, additionalPropertiesRef: TypeRef | undefined) {
if (this instanceof MapType) {
assert(properties.isEmpty(), "Cannot set properties on map type");
} else if (this._properties !== undefined) {
return panic("Tried to set object properties again");
}
if (this instanceof ClassType) {
assert(additionalPropertiesRef === undefined, "Cannot set additional properties of class type");
}
this._properties = properties;
this._additionalPropertiesRef = additionalPropertiesRef;
}
getProperties(): OrderedMap<string, ClassProperty> {
return defined(this._properties);
}
getSortedProperties(): OrderedMap<string, ClassProperty> {
const properties = this.getProperties();
const sortedKeys = properties.keySeq().sort();
const props = sortedKeys.map((k: string): [string, ClassProperty] => [k, defined(properties.get(k))]);
return OrderedMap(props);
}
get additionalProperties(): Type | undefined {
getAdditionalProperties(): Type | undefined {
assert(this._properties !== undefined, "Properties are not set yet");
if (this._additionalPropertiesRef === undefined) return undefined;
return this._additionalPropertiesRef.deref()[0];
}
get children(): OrderedSet<Type> {
const children = this.sortedProperties.map(p => p.type).toOrderedSet();
const additionalProperties = this.additionalProperties;
const children = this.getSortedProperties()
.map(p => p.type)
.toOrderedSet();
const additionalProperties = this.getAdditionalProperties();
if (additionalProperties === undefined) {
return children;
}
@ -341,38 +366,63 @@ export class ObjectType extends Type {
return false;
}
map(builder: TypeReconstituter, f: (tref: TypeRef) => TypeRef): TypeRef {
// const indent = " ".repeat(mapPath.length);
// const path = mapPath.join(".");
// console.log(`${indent} mapping class ${this.getCombinedName()} at ${path}`);
const properties = this.properties.map((p, _n) => {
// console.log(`${indent} property ${path}.${n}`);
// mapPath.push(n);
const result = new ClassProperty(f(p.typeRef), p.isOptional);
// mapPath.pop();
return result;
});
const additionalProperties = mapOptional(f, this._additionalPropertiesRef);
switch (this.kind) {
case "object":
assert(this.isFixed);
return builder.getUniqueObjectType(properties, additionalProperties);
case "map":
return builder.getMapType(defined(additionalProperties));
case "class":
if (this.isFixed) {
return builder.getUniqueClassType(true, properties);
} else {
return builder.getClassType(properties);
}
default:
return panic(`Invalid object type kind ${this.kind}`);
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
const maybePropertyTypes = builder.lookup(this.getProperties().map(cp => cp.typeRef));
const maybeAdditionalProperties = mapOptional(r => builder.lookup(r), this._additionalPropertiesRef);
if (
maybePropertyTypes !== undefined &&
(maybeAdditionalProperties !== undefined || this._additionalPropertiesRef === undefined)
) {
const properties = this.getProperties().map(
(cp, n) => new ClassProperty(defined(maybePropertyTypes.get(n)), cp.isOptional)
);
switch (this.kind) {
case "object":
assert(this.isFixed);
builder.getObjectType(properties, maybeAdditionalProperties);
break;
case "map":
builder.getMapType(defined(maybeAdditionalProperties));
break;
case "class":
if (this.isFixed) {
builder.getUniqueClassType(true, properties);
} else {
builder.getClassType(properties);
}
break;
default:
return panic(`Invalid object type kind ${this.kind}`);
}
} else {
switch (this.kind) {
case "object":
assert(this.isFixed);
builder.getUniqueObjectType(undefined, undefined);
break;
case "map":
builder.getUniqueMapType();
break;
case "class":
builder.getUniqueClassType(this.isFixed, undefined);
break;
default:
return panic(`Invalid object type kind ${this.kind}`);
}
const properties = this.getProperties().map(
cp => new ClassProperty(builder.reconstitute(cp.typeRef), cp.isOptional)
);
const additionalProperties = mapOptional(r => builder.reconstitute(r), this._additionalPropertiesRef);
builder.setObjectProperties(properties, additionalProperties);
}
}
protected structuralEqualityStep(other: ObjectType, queue: (a: Type, b: Type) => boolean): boolean {
const pa = this.properties;
const pb = other.properties;
const pa = this.getProperties();
const pb = other.getProperties();
if (pa.size !== pb.size) return false;
let failed = false;
pa.forEach((cpa, name) => {
@ -384,9 +434,11 @@ export class ObjectType extends Type {
});
if (failed) return false;
if ((this.additionalProperties === undefined) !== (other.additionalProperties === undefined)) return false;
if (this.additionalProperties === undefined || other.additionalProperties === undefined) return true;
return queue(this.additionalProperties, other.additionalProperties);
const thisAdditionalProperties = this.getAdditionalProperties();
const otherAdditionalProperties = other.getAdditionalProperties();
if ((thisAdditionalProperties === undefined) !== (otherAdditionalProperties === undefined)) return false;
if (thisAdditionalProperties === undefined || otherAdditionalProperties === undefined) return true;
return queue(thisAdditionalProperties, otherAdditionalProperties);
}
}
@ -394,7 +446,7 @@ export class ClassType extends ObjectType {
// @ts-ignore: This is initialized in the Type constructor
kind: "class";
constructor(typeRef: TypeRef, readonly isFixed: boolean, readonly properties: OrderedMap<string, ClassProperty>) {
constructor(typeRef: TypeRef, isFixed: boolean, properties: OrderedMap<string, ClassProperty> | undefined) {
super(typeRef, "class", isFixed, properties, undefined);
}
}
@ -403,12 +455,13 @@ export class MapType extends ObjectType {
// @ts-ignore: This is initialized in the Type constructor
readonly kind: "map";
constructor(typeRef: TypeRef, valuesRef: TypeRef) {
constructor(typeRef: TypeRef, valuesRef: TypeRef | undefined) {
super(typeRef, "map", false, OrderedMap(), valuesRef);
}
// FIXME: Remove and use `getAdditionalProperties()` instead.
get values(): Type {
return defined(this.additionalProperties);
return defined(this.getAdditionalProperties());
}
}
@ -446,9 +499,8 @@ export class EnumType extends Type {
return false;
}
map(builder: TypeReconstituter, _: (tref: TypeRef) => TypeRef): TypeRef {
// console.log(`${mapIndentation()}mapping ${this.kind}`);
return builder.getEnumType(this.cases);
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
builder.getEnumType(this.cases);
}
protected structuralEqualityStep(other: EnumType, _queue: (a: Type, b: Type) => void): boolean {
@ -492,7 +544,7 @@ export abstract class SetOperationType extends Type {
return false;
}
protected structuralEqualityStep(other: UnionType, queue: (a: Type, b: Type) => boolean): boolean {
protected structuralEqualityStep(other: SetOperationType, queue: (a: Type, b: Type) => boolean): boolean {
return setOperationCasesEqual(this.members, other.members, queue);
}
}
@ -509,10 +561,15 @@ export class IntersectionType extends SetOperationType {
return panic("isNullable not implemented for IntersectionType");
}
map(builder: TypeReconstituter, f: (tref: TypeRef) => TypeRef): TypeRef {
const members = this.getMemberRefs().map(f);
// FIXME: Eventually switch to non-unique
return builder.getUniqueIntersectionType(members);
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
const memberRefs = this.getMemberRefs();
const maybeMembers = builder.lookup(memberRefs);
if (maybeMembers === undefined) {
builder.getUniqueIntersectionType();
builder.setSetOperationMembers(builder.reconstitute(memberRefs));
} else {
builder.getIntersectionType(maybeMembers);
}
}
}
@ -562,9 +619,15 @@ export class UnionType extends SetOperationType {
return true;
}
map(builder: TypeReconstituter, f: (tref: TypeRef) => TypeRef): TypeRef {
const members = this.getMemberRefs().map(f);
return builder.getUnionType(members);
reconstitute<T extends Type>(builder: TypeReconstituter<T>): void {
const memberRefs = this.getMemberRefs();
const maybeMembers = builder.lookup(memberRefs);
if (maybeMembers === undefined) {
builder.getUniqueUnionType();
builder.setSetOperationMembers(builder.reconstitute(memberRefs));
} else {
builder.getUnionType(maybeMembers);
}
}
}

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

@ -1,6 +1,6 @@
"use strict";
import { Map, OrderedMap, OrderedSet, Set } from "immutable";
import { Map, OrderedMap, OrderedSet, Set, Collection, isCollection } from "immutable";
import {
PrimitiveTypeKind,
@ -28,7 +28,7 @@ import {
emptyTypeAttributes,
makeTypeAttributesInferred
} from "./TypeAttributes";
import { defined, assert, panic, assertNever, setUnion } from "./Support";
import { defined, assert, panic, assertNever, setUnion, mapOptional } from "./Support";
export type TypeRefCallback = (index: number) => void;
@ -129,6 +129,8 @@ function provenanceToString(p: Set<TypeRef>): string {
.join(",");
}
// FIXME: Don't infer provenance. All original types should be present in
// non-inferred form in the final graph.
export const provenanceTypeAttributeKind = new TypeAttributeKind<Set<TypeRef>>(
"provenance",
setUnion,
@ -148,6 +150,15 @@ export const NoStringTypeMapping: StringTypeMapping = {
dateTime: "date-time"
};
function forwardIfNecessary(forwardingRef: TypeRef | undefined, maybeRef: TypeRef): TypeRef;
function forwardIfNecessary(forwardingRef: TypeRef | undefined, maybeRef: TypeRef | undefined): TypeRef | undefined;
function forwardIfNecessary(forwardingRef: TypeRef | undefined, maybeRef: TypeRef | undefined): TypeRef | undefined {
if (forwardingRef !== undefined && maybeRef !== undefined) {
forwardingRef.resolve(maybeRef);
}
return maybeRef;
}
export class TypeBuilder {
readonly typeGraph: TypeGraph;
@ -264,6 +275,7 @@ export class TypeBuilder {
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();
getPrimitiveType(kind: PrimitiveTypeKind, forwardingRef?: TypeRef): TypeRef {
assert(kind !== "string", "Use getStringType to create strings");
@ -273,7 +285,7 @@ export class TypeBuilder {
if (kind === "string") {
return this.getStringType(undefined, undefined, forwardingRef);
}
let tref = this._primitiveTypes.get(kind);
let tref = 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);
@ -301,14 +313,14 @@ export class TypeBuilder {
this._noEnumStringType = this.addType(forwardingRef, tr => new StringType(tr, undefined), undefined);
}
this.addAttributes(this._noEnumStringType, attributes);
return this._noEnumStringType;
return forwardIfNecessary(forwardingRef, this._noEnumStringType);
}
return this.addType(forwardingRef, tr => new StringType(tr, cases), attributes);
}
getEnumType(attributes: TypeAttributes, cases: OrderedSet<string>, forwardingRef?: TypeRef): TypeRef {
const unorderedCases = cases.toSet();
let tref = this._enumTypes.get(unorderedCases);
let tref = 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);
@ -320,11 +332,11 @@ export class TypeBuilder {
getUniqueObjectType(
attributes: TypeAttributes,
properties: OrderedMap<string, ClassProperty>,
properties: OrderedMap<string, ClassProperty> | undefined,
additionalProperties: TypeRef | undefined,
forwardingRef?: TypeRef
): TypeRef {
properties = this.modifyPropertiesIfNecessary(properties);
properties = mapOptional(p => this.modifyPropertiesIfNecessary(p), properties);
return this.addType(
forwardingRef,
tref => new ObjectType(tref, "object", true, properties, additionalProperties),
@ -332,8 +344,12 @@ export class TypeBuilder {
);
}
getUniqueMapType(forwardingRef?: TypeRef): TypeRef {
return this.addType(forwardingRef, tr => new MapType(tr, undefined), undefined);
}
getMapType(values: TypeRef, forwardingRef?: TypeRef): TypeRef {
let tref = this._mapTypes.get(values);
let tref = forwardIfNecessary(forwardingRef, this._mapTypes.get(values));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new MapType(tr, values), undefined);
this._mapTypes = this._mapTypes.set(values, tref);
@ -341,8 +357,24 @@ export class TypeBuilder {
return tref;
}
setObjectProperties(
ref: TypeRef,
properties: OrderedMap<string, ClassProperty>,
additionalProperties: TypeRef | undefined
): 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);
}
getUniqueArrayType(forwardingRef?: TypeRef): TypeRef {
return this.addType(forwardingRef, tr => new ArrayType(tr, undefined), undefined);
}
getArrayType(items: TypeRef, forwardingRef?: TypeRef): TypeRef {
let tref = this._arrayTypes.get(items);
let tref = forwardIfNecessary(forwardingRef, this._arrayTypes.get(items));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new ArrayType(tr, items), undefined);
this._arrayTypes = this._arrayTypes.set(items, tref);
@ -350,6 +382,14 @@ export class TypeBuilder {
return tref;
}
setArrayItems(ref: TypeRef, items: TypeRef): void {
const type = ref.deref()[0];
if (!(type instanceof ArrayType)) {
return panic("Tried to set items of non-array type");
}
type.setItems(items);
}
modifyPropertiesIfNecessary(properties: OrderedMap<string, ClassProperty>): OrderedMap<string, ClassProperty> {
if (this.alphabetizeProperties) {
properties = properties.sortBy((_, n) => n);
@ -366,11 +406,8 @@ export class TypeBuilder {
forwardingRef?: TypeRef
): TypeRef {
properties = this.modifyPropertiesIfNecessary(properties);
let tref = this._classTypes.get(properties.toMap());
// FIXME: It's not clear to me that the `forwardingRef` condition here
// might actually ever be true. And if it can, shouldn't we also have
// it in all the other `getXXX` methods here?
if ((forwardingRef !== undefined && forwardingRef.maybeIndex !== undefined) || tref === undefined) {
let tref = forwardIfNecessary(forwardingRef, this._classTypes.get(properties.toMap()));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new ClassType(tr, false, properties), attributes);
this._classTypes = this._classTypes.set(properties.toMap(), tref);
} else {
@ -384,16 +421,16 @@ export class TypeBuilder {
getUniqueClassType(
attributes: TypeAttributes,
isFixed: boolean,
properties: OrderedMap<string, ClassProperty>,
properties: OrderedMap<string, ClassProperty> | undefined,
forwardingRef?: TypeRef
): TypeRef {
properties = this.modifyPropertiesIfNecessary(properties);
properties = mapOptional(p => this.modifyPropertiesIfNecessary(p), properties);
return this.addType(forwardingRef, tref => new ClassType(tref, isFixed, properties), attributes);
}
getUnionType(attributes: TypeAttributes, members: OrderedSet<TypeRef>, forwardingRef?: TypeRef): TypeRef {
const unorderedMembers = members.toSet();
let tref = this._unionTypes.get(unorderedMembers);
let tref = forwardIfNecessary(forwardingRef, this._unionTypes.get(unorderedMembers));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new UnionType(tr, members), attributes);
this._unionTypes = this._unionTypes.set(unorderedMembers, tref);
@ -411,6 +448,18 @@ export class TypeBuilder {
return this.addType(forwardingRef, tref => new UnionType(tref, members), attributes);
}
getIntersectionType(attributes: TypeAttributes, members: OrderedSet<TypeRef>, forwardingRef?: TypeRef): TypeRef {
const unorderedMembers = members.toSet();
let tref = forwardIfNecessary(forwardingRef, this._intersectionTypes.get(unorderedMembers));
if (tref === undefined) {
tref = this.addType(forwardingRef, tr => new IntersectionType(tr, members), attributes);
this._intersectionTypes = this._intersectionTypes.set(unorderedMembers, tref);
} else {
this.addAttributes(tref, attributes);
}
return tref;
}
getUniqueIntersectionType(
attributes: TypeAttributes,
members: OrderedSet<TypeRef> | undefined,
@ -438,78 +487,170 @@ export interface TypeLookerUp {
registerUnion(typeRefs: TypeRef[], reconstituted: TypeRef): void;
}
export class TypeReconstituter {
export class TypeReconstituter<T extends Type> {
private _wasUsed: boolean = false;
private _typeRef: TypeRef | undefined = undefined;
constructor(
private readonly _typeBuilder: TypeBuilder,
private readonly _typeBuilder: GraphRewriteBuilder<T>,
private readonly _makeClassUnique: boolean,
private readonly _typeAttributes: TypeAttributes,
private readonly _forwardingRef: TypeRef
private readonly _forwardingRef: TypeRef,
private readonly _register: (tref: TypeRef) => void
) {}
private useBuilder(): TypeBuilder {
private builderForNewType(): GraphRewriteBuilder<T> {
assert(!this._wasUsed, "TypeReconstituter used more than once");
this._wasUsed = true;
return this._typeBuilder;
}
private addAttributes(tref: TypeRef): TypeRef {
private builderForSetting(): GraphRewriteBuilder<T> {
assert(this._wasUsed && this._typeRef !== undefined, "Can't set type members before constructing a type");
return this._typeBuilder;
}
getResult(): TypeRef {
if (this._typeRef === undefined) {
return panic("Type was not reconstituted");
}
return this._typeRef;
}
// FIXME: Do registration automatically.
private register(tref: TypeRef): void {
assert(this._typeRef === undefined, "Cannot register a type twice");
this._typeRef = tref;
this._register(tref);
}
private registerAndAddAttributes(tref: TypeRef): void {
this._typeBuilder.addAttributes(tref, this._typeAttributes);
return tref;
this.register(tref);
}
getPrimitiveType(kind: PrimitiveTypeKind): TypeRef {
return this.addAttributes(this.useBuilder().getPrimitiveType(kind, this._forwardingRef));
lookup(tref: TypeRef): TypeRef | undefined;
lookup<C extends Collection<any, TypeRef>>(trefs: C): C | undefined;
lookup<C extends Collection<any, TypeRef>>(trefs: TypeRef | C): TypeRef | C | undefined {
assert(!this._wasUsed, "Cannot lookup constituents after building type");
if (isCollection(trefs)) {
const maybeRefs = trefs.map(tref => this._typeBuilder.lookupTypeRefs([tref], undefined, false));
if (maybeRefs.some(tref => tref === undefined)) return undefined;
return maybeRefs as C;
}
return this._typeBuilder.lookupTypeRefs([trefs], undefined, false);
}
getStringType(enumCases: OrderedMap<string, number> | undefined): TypeRef {
return this.useBuilder().getStringType(this._typeAttributes, enumCases, this._forwardingRef);
reconstitute(tref: TypeRef): TypeRef;
reconstitute<C extends Collection<any, TypeRef>>(trefs: C): C;
reconstitute<C extends Collection<any, TypeRef>>(trefs: TypeRef | C): TypeRef | C {
assert(this._wasUsed, "Cannot reconstitute constituents before building type");
if (isCollection(trefs)) {
return trefs.map(tref => this._typeBuilder.reconstituteTypeRef(tref)) as C;
}
return this._typeBuilder.reconstituteTypeRef(trefs);
}
getEnumType(cases: OrderedSet<string>): TypeRef {
return this.useBuilder().getEnumType(defined(this._typeAttributes), cases, this._forwardingRef);
getPrimitiveType(kind: PrimitiveTypeKind): void {
this.registerAndAddAttributes(this.builderForNewType().getPrimitiveType(kind, this._forwardingRef));
}
getMapType(values: TypeRef): TypeRef {
return this.addAttributes(this.useBuilder().getMapType(values, this._forwardingRef));
getStringType(enumCases: OrderedMap<string, number> | undefined): void {
this.register(this.builderForNewType().getStringType(this._typeAttributes, enumCases, this._forwardingRef));
}
getArrayType(items: TypeRef): TypeRef {
return this.addAttributes(this.useBuilder().getArrayType(items, this._forwardingRef));
getEnumType(cases: OrderedSet<string>): void {
this.register(this.builderForNewType().getEnumType(this._typeAttributes, cases, this._forwardingRef));
}
getUniqueMapType(): void {
this.registerAndAddAttributes(this.builderForNewType().getUniqueMapType(this._forwardingRef));
}
getMapType(values: TypeRef): void {
this.registerAndAddAttributes(this.builderForNewType().getMapType(values, this._forwardingRef));
}
getUniqueArrayType(): void {
this.registerAndAddAttributes(this.builderForNewType().getUniqueArrayType(this._forwardingRef));
}
getArrayType(items: TypeRef): void {
this.registerAndAddAttributes(this.builderForNewType().getArrayType(items, this._forwardingRef));
}
setArrayItems(items: TypeRef): void {
this.builderForSetting().setArrayItems(this.getResult(), items);
}
getObjectType(properties: OrderedMap<string, ClassProperty>, additionalProperties: TypeRef | undefined): void {
this.register(
this.builderForNewType().getUniqueObjectType(
this._typeAttributes,
properties,
additionalProperties,
this._forwardingRef
)
);
}
getUniqueObjectType(
properties: OrderedMap<string, ClassProperty> | undefined,
additionalProperties: TypeRef | undefined
): void {
this.register(
this.builderForNewType().getUniqueObjectType(
this._typeAttributes,
properties,
additionalProperties,
this._forwardingRef
)
);
}
getClassType(properties: OrderedMap<string, ClassProperty>): void {
if (this._makeClassUnique) {
this.getUniqueClassType(false, properties);
return;
}
this.register(this.builderForNewType().getClassType(this._typeAttributes, properties, this._forwardingRef));
}
getUniqueClassType(isFixed: boolean, properties: OrderedMap<string, ClassProperty> | undefined): void {
this.register(
this.builderForNewType().getUniqueClassType(this._typeAttributes, isFixed, properties, this._forwardingRef)
);
}
setObjectProperties(
properties: OrderedMap<string, ClassProperty>,
additionalProperties: TypeRef | undefined
): TypeRef {
return this.addAttributes(
this.useBuilder().getUniqueObjectType(defined(this._typeAttributes), properties, additionalProperties)
): void {
this.builderForSetting().setObjectProperties(this.getResult(), properties, additionalProperties);
}
getUnionType(members: OrderedSet<TypeRef>): void {
this.register(this.builderForNewType().getUnionType(this._typeAttributes, members, this._forwardingRef));
}
getUniqueUnionType(): void {
this.register(
this.builderForNewType().getUniqueUnionType(this._typeAttributes, undefined, this._forwardingRef)
);
}
getClassType(properties: OrderedMap<string, ClassProperty>): TypeRef {
if (this._makeClassUnique) {
return this.getUniqueClassType(false, properties);
}
return this.useBuilder().getClassType(defined(this._typeAttributes), properties, this._forwardingRef);
getIntersectionType(members: OrderedSet<TypeRef>): void {
this.register(this.builderForNewType().getIntersectionType(this._typeAttributes, members, this._forwardingRef));
}
getUniqueClassType(isFixed: boolean, properties: OrderedMap<string, ClassProperty>): TypeRef {
return this.useBuilder().getUniqueClassType(
defined(this._typeAttributes),
isFixed,
properties,
this._forwardingRef
getUniqueIntersectionType(members?: OrderedSet<TypeRef>): void {
this.register(
this.builderForNewType().getUniqueIntersectionType(this._typeAttributes, members, this._forwardingRef)
);
}
getUnionType(members: OrderedSet<TypeRef>): TypeRef {
return this.useBuilder().getUnionType(defined(this._typeAttributes), members, this._forwardingRef);
}
getUniqueIntersectionType(members: OrderedSet<TypeRef>): TypeRef {
return this.useBuilder().getUniqueIntersectionType(defined(this._typeAttributes), members, this._forwardingRef);
setSetOperationMembers(members: OrderedSet<TypeRef>): void {
this.builderForSetting().setSetOperationMembers(this.getResult(), members);
}
}
@ -520,12 +661,15 @@ export class GraphRewriteBuilder<T extends Type> extends TypeBuilder implements
private _lostTypeAttributes: boolean = false;
private _printIndent = 0;
constructor(
private readonly _originalGraph: TypeGraph,
stringTypeMapping: StringTypeMapping,
alphabetizeProperties: boolean,
graphHasProvenanceAttributes: boolean,
setsToReplace: T[][],
private readonly _debugPrintReconstitution: boolean,
private readonly _replacer: (
typesToReplace: Set<T>,
builder: GraphRewriteBuilder<T>,
@ -578,12 +722,36 @@ export class GraphRewriteBuilder<T extends Type> extends TypeBuilder implements
private forceReconstituteTypeRef(originalRef: TypeRef, maybeForwardingRef?: TypeRef): TypeRef {
return this.withForwardingRef(maybeForwardingRef, (forwardingRef: TypeRef): TypeRef => {
this._reconstitutedTypes = this._reconstitutedTypes.set(originalRef.getIndex(), forwardingRef);
const [originalType, originalNames] = originalRef.deref();
return originalType.map(
new TypeReconstituter(this, this.alphabetizeProperties, originalNames, forwardingRef),
(ref: TypeRef) => this.reconstituteTypeRef(ref)
const [originalType, originalAttributes] = originalRef.deref();
const index = originalRef.getIndex();
if (this._debugPrintReconstitution) {
console.log(`${" ".repeat(this._printIndent)}reconstituting ${index}`);
this._printIndent += 1;
}
const reconstituter = new TypeReconstituter(
this,
this.alphabetizeProperties,
originalAttributes,
forwardingRef,
tref => {
if (this._debugPrintReconstitution) {
this._printIndent -= 1;
console.log(`${" ".repeat(this._printIndent)}reconstituted ${index} as ${tref.getIndex()}`);
}
assert(tref.equals(forwardingRef), "We didn't pass the forwarding ref");
const alreadyReconstitutedType = this._reconstitutedTypes.get(index);
if (alreadyReconstitutedType === undefined) {
this._reconstitutedTypes = this._reconstitutedTypes.set(index, tref);
} else {
assert(tref.equals(alreadyReconstitutedType), "We reconstituted a type twice differently");
}
}
);
originalType.reconstitute(reconstituter);
return reconstituter.getResult();
});
}
@ -610,7 +778,7 @@ export class GraphRewriteBuilder<T extends Type> extends TypeBuilder implements
// If the union of these type refs have been, or are supposed to be, reconstituted to
// one target type, return it. Otherwise return undefined.
lookupTypeRefs(typeRefs: TypeRef[], forwardingRef?: TypeRef): TypeRef | undefined {
lookupTypeRefs(typeRefs: TypeRef[], forwardingRef?: TypeRef, replaceSet: boolean = true): TypeRef | undefined {
this.assertTypeRefsToReconstitute(typeRefs, forwardingRef);
// Check whether we have already reconstituted them. That means ensuring
@ -652,6 +820,7 @@ export class GraphRewriteBuilder<T extends Type> extends TypeBuilder implements
}
}
// Yes, this set is requested to be replaced, so do it.
if (!replaceSet) return undefined;
return this.replaceSet(maybeSet, forwardingRef);
}

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

@ -242,6 +242,7 @@ export class TypeGraph {
stringTypeMapping: StringTypeMapping,
alphabetizeProperties: boolean,
replacementGroups: T[][],
debugPrintReconstitution: boolean,
replacer: (typesToReplace: Set<T>, builder: GraphRewriteBuilder<T>, forwardingRef: TypeRef) => TypeRef,
force: boolean = false
): TypeGraph {
@ -257,6 +258,7 @@ export class TypeGraph {
alphabetizeProperties,
this._haveProvenanceAttributes,
replacementGroups,
debugPrintReconstitution,
replacer
);
const newGraph = builder.finish();
@ -285,6 +287,7 @@ export class TypeGraph {
NoStringTypeMapping,
alphabetizeProperties,
[],
false,
(_t, _b) => mustNotBeCalled(),
true
);
@ -337,7 +340,11 @@ export class TypeGraph {
}
}
export function noneToAny(graph: TypeGraph, stringTypeMapping: StringTypeMapping): TypeGraph {
export function noneToAny(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
debugPrintReconstitution: boolean
): TypeGraph {
const noneTypes = graph.allTypesUnordered().filter(t => t.kind === "none");
if (noneTypes.size === 0) {
return graph;
@ -348,6 +355,7 @@ export function noneToAny(graph: TypeGraph, stringTypeMapping: StringTypeMapping
stringTypeMapping,
false,
[noneTypes.toArray()],
debugPrintReconstitution,
(types, builder, forwardingRef) => {
const tref = builder.getPrimitiveType("any", forwardingRef);
const attributes = combineTypeAttributesOfTypes(types);
@ -357,9 +365,13 @@ export function noneToAny(graph: TypeGraph, stringTypeMapping: StringTypeMapping
);
}
export function optionalToNullable(graph: TypeGraph, stringTypeMapping: StringTypeMapping): TypeGraph {
export function optionalToNullable(
graph: TypeGraph,
stringTypeMapping: StringTypeMapping,
debugPrintReconstitution: boolean
): TypeGraph {
function rewriteClass(c: ClassType, builder: GraphRewriteBuilder<ClassType>, forwardingRef: TypeRef): TypeRef {
const properties = c.properties.map((p, name) => {
const properties = c.getProperties().map((p, name) => {
const t = p.type;
let ref: TypeRef;
if (!p.isOptional || t.isNullable) {
@ -389,7 +401,7 @@ export function optionalToNullable(graph: TypeGraph, stringTypeMapping: StringTy
const classesWithOptional = graph
.allTypesUnordered()
.filter(t => t instanceof ClassType && t.properties.some(p => p.isOptional));
.filter(t => t instanceof ClassType && t.getProperties().some(p => p.isOptional));
const replacementGroups = classesWithOptional.map(c => [c as ClassType]).toArray();
if (classesWithOptional.size === 0) {
return graph;
@ -399,6 +411,7 @@ export function optionalToNullable(graph: TypeGraph, stringTypeMapping: StringTy
stringTypeMapping,
false,
replacementGroups,
debugPrintReconstitution,
(setOfClass, builder, forwardingRef) => {
assert(setOfClass.size === 1);
const c = defined(setOfClass.first());

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

@ -22,7 +22,7 @@ function getCliqueProperties(
let lostTypeAttributes = false;
let propertyNames = OrderedSet<string>();
for (const o of clique) {
propertyNames = propertyNames.union(o.properties.keySeq());
propertyNames = propertyNames.union(o.getProperties().keySeq());
}
let properties = propertyNames
@ -30,7 +30,7 @@ function getCliqueProperties(
.map(name => [name, OrderedSet(), false] as [string, OrderedSet<Type>, boolean]);
let additionalProperties: OrderedSet<Type> | undefined = undefined;
for (const o of clique) {
let additional = o.additionalProperties;
let additional = o.getAdditionalProperties();
if (additional !== undefined) {
if (additionalProperties === undefined) {
additionalProperties = OrderedSet();
@ -42,7 +42,7 @@ function getCliqueProperties(
for (let i = 0; i < properties.length; i++) {
let [name, types, isOptional] = properties[i];
const maybeProperty = o.properties.get(name);
const maybeProperty = o.getProperties().get(name);
if (maybeProperty === undefined) {
isOptional = true;
if (additional !== undefined && additional.kind !== "any") {
@ -85,10 +85,10 @@ function countProperties(
let hasAdditionalProperties = false;
let hasNonAnyAdditionalProperties = false;
for (const o of clique) {
if (!o.properties.isEmpty()) {
if (!o.getProperties().isEmpty()) {
hasProperties = true;
}
const additional = o.additionalProperties;
const additional = o.getAdditionalProperties();
if (additional !== undefined) {
hasAdditionalProperties = true;
if (additional.kind !== "any") {
@ -143,11 +143,18 @@ export class UnifyUnionBuilder extends UnionBuilder<TypeBuilder & TypeLookerUp,
const { hasProperties, hasAdditionalProperties, hasNonAnyAdditionalProperties } = countProperties(objectTypes);
if (!this._makeObjectTypes && (hasNonAnyAdditionalProperties || (!hasProperties && hasAdditionalProperties))) {
const propertyTypes = unionOfSets(objectTypes.map(o => o.properties.map(cp => cp.typeRef).toOrderedSet()));
const propertyTypes = unionOfSets(
objectTypes.map(o =>
o
.getProperties()
.map(cp => cp.typeRef)
.toOrderedSet()
)
);
const additionalPropertyTypes = OrderedSet(
objectTypes
.filter(o => o.additionalProperties !== undefined)
.map(o => defined(o.additionalProperties).typeRef)
.filter(o => o.getAdditionalProperties() !== undefined)
.map(o => defined(o.getAdditionalProperties()).typeRef)
);
const allPropertyTypes = propertyTypes.union(additionalPropertyTypes).toArray();
const tref = this.typeBuilder.getMapType(this._unifyTypes(allPropertyTypes, emptyTypeAttributes));

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

@ -425,7 +425,7 @@ function makeOptionDefinitions(targetLanguages: TargetLanguage[]): OptionDefinit
name: "debug",
type: String,
typeLabel: "OPTIONS",
description: "Comma separated debug options: print-graph,provenance"
description: "Comma separated debug options: print-graph, provenance, print-reconstitution"
},
{
name: "telemetry",
@ -741,12 +741,15 @@ export async function makeQuicktypeOptions(
let debugPrintGraph = false;
let checkProvenance = false;
let debugPrintReconstitution = false;
if (options.debug !== undefined) {
const components = options.debug.split(",");
for (let component of components) {
component = component.trim();
if (component === "print-graph") {
debugPrintGraph = true;
} else if (component === "print-reconstitution") {
debugPrintReconstitution = true;
} else if (component === "provenance") {
checkProvenance = true;
} else {
@ -778,7 +781,8 @@ export async function makeQuicktypeOptions(
outputFilename: mapOptional(path.basename, options.out),
schemaStore: new FetchingJSONSchemaStore(),
debugPrintGraph,
checkProvenance
checkProvenance,
debugPrintReconstitution
};
}

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

@ -63,6 +63,7 @@ export interface Options {
schemaStore: JSONSchemaStore | undefined;
debugPrintGraph: boolean;
checkProvenance: boolean;
debugPrintReconstitution: boolean;
}
const defaultOptions: Options = {
@ -84,7 +85,8 @@ const defaultOptions: Options = {
outputFilename: "stdout",
schemaStore: undefined,
debugPrintGraph: false,
checkProvenance: false
checkProvenance: false,
debugPrintReconstitution: false
};
export class Run {
@ -155,16 +157,28 @@ export class Run {
graph.printGraph();
}
const debugPrintReconstitution = this._options.debugPrintReconstitution === true;
let unionsDone = false;
if (!schemaInputs.isEmpty()) {
let intersectionsDone = false;
do {
const graphBeforeRewrites = graph;
if (!intersectionsDone) {
[graph, intersectionsDone] = resolveIntersections(graph, stringTypeMapping);
[graph, intersectionsDone] = resolveIntersections(
graph,
stringTypeMapping,
debugPrintReconstitution
);
}
if (!unionsDone) {
[graph, unionsDone] = flattenUnions(graph, stringTypeMapping, conflateNumbers, true);
[graph, unionsDone] = flattenUnions(
graph,
stringTypeMapping,
conflateNumbers,
true,
debugPrintReconstitution
);
}
if (graph === graphBeforeRewrites) {
@ -173,9 +187,21 @@ export class Run {
} while (!intersectionsDone || !unionsDone);
}
graph = replaceObjectType(graph, stringTypeMapping, conflateNumbers, targetLanguage.supportsFullObjectType);
graph = replaceObjectType(
graph,
stringTypeMapping,
conflateNumbers,
targetLanguage.supportsFullObjectType,
debugPrintReconstitution
);
do {
[graph, unionsDone] = flattenUnions(graph, stringTypeMapping, conflateNumbers, false);
[graph, unionsDone] = flattenUnions(
graph,
stringTypeMapping,
conflateNumbers,
false,
debugPrintReconstitution
);
} while (!unionsDone);
if (this._options.findSimilarClassesSchemaURI !== undefined) {
@ -183,18 +209,24 @@ export class Run {
}
if (this._options.combineClasses) {
graph = combineClasses(graph, stringTypeMapping, this._options.alphabetizeProperties, conflateNumbers);
graph = combineClasses(
graph,
stringTypeMapping,
this._options.alphabetizeProperties,
conflateNumbers,
debugPrintReconstitution
);
}
if (doInferEnums) {
graph = inferEnums(graph, stringTypeMapping);
graph = inferEnums(graph, stringTypeMapping, debugPrintReconstitution);
}
graph = flattenStrings(graph, stringTypeMapping);
graph = flattenStrings(graph, stringTypeMapping, debugPrintReconstitution);
if (this._options.inferMaps) {
graph = inferMaps(graph, stringTypeMapping, conflateNumbers);
graph = inferMaps(graph, stringTypeMapping, conflateNumbers, debugPrintReconstitution);
}
graph = noneToAny(graph, stringTypeMapping);
graph = noneToAny(graph, stringTypeMapping, debugPrintReconstitution);
if (!targetLanguage.supportsOptionalClassProperties) {
graph = optionalToNullable(graph, stringTypeMapping);
graph = optionalToNullable(graph, stringTypeMapping, debugPrintReconstitution);
}
// Sometimes we combine classes in ways that will the order come out
// differently compared to what it would be from the equivalent schema,