Skip to content

Commit

Permalink
Name intermediate Leaf types.
Browse files Browse the repository at this point in the history
Rather than inlining a "leaf" type as an intersection in a big union,
name it. This should hopefully help with google#34.

This is based on advice from @amcasey regarding compiler performance:

> I've definitely seen some impressive perf improvements from naming
> intermediate types (because it can short-circuit structural type
> comparison, which is very expensive).

This fits neatly with my initial description of the Schema.org type
system here:
https://blog.eyas.sh/2019/05/modeling-schema-org-schema-with-typescript-the-power-and-limitations-of-the-typescript-type-system/

There's still some room for improvements:

- Some intermediate types are simply aliases, can we remove these?
- 'DataType's don't necessarily fit neatly here.
  • Loading branch information
Eyas committed May 5, 2020
1 parent b877e5e commit df94487
Showing 1 changed file with 58 additions and 21 deletions.
79 changes: 58 additions & 21 deletions src/ts/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export class Class {
protected baseName() {
return toClassName(this.subject) + 'Base';
}

protected leafName() {
return toClassName(this.subject) + 'Leaf';
}

private className() {
return toClassName(this.subject);
}
Expand Down Expand Up @@ -230,7 +235,40 @@ export class Class {
);
}

private nonEnumType(context: Context, skipDeprecated: boolean): TypeNode {
private leafDecl(context: Context): TypeAliasDeclaration | undefined {
// If we inherit from a DataType (~= a Built In), then the type is _not_
// represented as a node. Skip the leaf type.
//
// TODO: This should probably be modeled differently given the advent of 'PronounceableText'.
//
// That is, 'PronounceableText' inherits 'Text', but does not _extend_ string per se;
// string, in Text, is a leaf.
//
// This breaks down because, e.g. 'URL' also inherits 'Text', but is string as well.
if (this.inheritsDataType()) {
return undefined;
}

const baseTypeReference = createTypeReferenceNode(
this.baseName(),
/*typeArguments=*/ []
);

const thisType = createIntersectionTypeNode([
createTypeLiteralNode([new TypeProperty(this.subject).toNode(context)]),
baseTypeReference,
]);

return createTypeAliasDeclaration(
/*decorators=*/ [],
/*modifiers=*/ [],
this.leafName(),
/*typeParameters=*/ [],
thisType
);
}

private nonEnumType(skipDeprecated: boolean): TypeNode {
this.children.sort((a, b) => CompareKeys(a.subject, b.subject));
const children = this.children
.filter(child => !(child.deprecated && skipDeprecated))
Expand All @@ -250,39 +288,33 @@ export class Class {
? children[0]
: createParenthesizedType(createUnionTypeNode(children));

const baseTypeReference = createTypeReferenceNode(
this.baseName(),
const leafTypeReference = createTypeReferenceNode(
// If we inherit from a DataType (~= a Built In), then the type is _not_
// represented as a node. Skip the leaf type.
//
// TODO: This should probably be modeled differently given the advent of 'PronounceableText'.
// See the note in 'leafDecl' for more details.
this.inheritsDataType() ? this.baseName() : this.leafName(),
/*typeArguments=*/ []
);

// If we inherit from a DataType (~= a Built In), then the type is _not_
// represented as a node. Skip the leaf type.
const thisType = this.inheritsDataType()
? baseTypeReference
: createIntersectionTypeNode([
createTypeLiteralNode([
new TypeProperty(this.subject).toNode(context),
]),
baseTypeReference,
]);

if (childrenNode) {
return createUnionTypeNode([thisType, childrenNode]);
return createUnionTypeNode([leafTypeReference, childrenNode]);
} else {
return thisType;
return leafTypeReference;
}
}

private totalType(context: Context, skipDeprecated: boolean): TypeNode {
private totalType(skipDeprecated: boolean): TypeNode {
const isEnum = this._enums.size > 0;

if (isEnum) {
return createUnionTypeNode([
...this.enums().map(e => e.toTypeLiteral()),
createParenthesizedType(this.nonEnumType(context, skipDeprecated)),
createParenthesizedType(this.nonEnumType(skipDeprecated)),
]);
} else {
return this.nonEnumType(context, skipDeprecated);
return this.nonEnumType(skipDeprecated);
}
}

Expand All @@ -309,7 +341,7 @@ export class Class {
}

toNode(context: Context, skipDeprecated: boolean): readonly Statement[] {
const typeValue: TypeNode = this.totalType(context, skipDeprecated);
const typeValue: TypeNode = this.totalType(skipDeprecated);
const declaration = withComments(
this.comment,
createTypeAliasDeclaration(
Expand All @@ -326,9 +358,13 @@ export class Class {
// type XyzBase = (Parents) & {
// ... props;
// };
// // Leaf:
// export type XyzLeaf = XyzBase & {
// '@type': 'Xyz'
// }
// // Complete Type ----------------------------//
// export type Xyz = "Enum1"|"Enum2"|... // Enum Piece: Optional.
// |XyzBase&{'@type': 'Xyz'} // 'Leaf' Piece.
// |XyzLeaf // 'Leaf' Piece.
// |Child1|Child2|... // Child Piece: Optional.
// // Enum Values: Optional --------------------//
// export const Xyz = {
Expand All @@ -339,6 +375,7 @@ export class Class {
// //-------------------------------------------//
return arrayOf<Statement>(
this.baseDecl(skipDeprecated, context),
this.leafDecl(context),
declaration,
this.enumDecl()
);
Expand Down

0 comments on commit df94487

Please sign in to comment.