Skip to content

Commit

Permalink
Optimize deferred type references (#36607)
Browse files Browse the repository at this point in the history
* Improve reasoning about when to create deferred type references

* Accept new baselines

* Fix minor issues

* Handle default type arguments case in isDeferredTypeReferenceNode
  • Loading branch information
ahejlsberg authored Feb 6, 2020
1 parent 0a16032 commit de37c87
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 17 deletions.
50 changes: 45 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11090,7 +11090,7 @@ namespace ts {
return errorType;
}
}
if (node.kind === SyntaxKind.TypeReference && isAliasedType(node)) {
if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(<TypeReferenceNode>node, length(node.typeArguments) !== typeParameters.length)) {
return createDeferredTypeReference(<GenericType>type, <TypeReferenceNode>node, /*mapper*/ undefined);
}
// In a type reference, the outer type parameters of the referenced class or interface are automatically
Expand Down Expand Up @@ -11552,10 +11552,19 @@ namespace ts {
return getTupleTypeOfArity(node.elementTypes.length, minLength, !!restElement, readonly, /*associatedNames*/ undefined);
}

// Return true if the given type reference node is directly aliased or if it needs to be deferred
// because it is possibly contained in a circular chain of eagerly resolved types.
function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) {
return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && (
node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) :
node.kind === SyntaxKind.TupleType ? some(node.elementTypes, mayResolveTypeAlias) :
hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias));
}

// Return true when the given node is transitively contained in type constructs that eagerly
// resolve their constituent types. We include SyntaxKind.TypeReference because type arguments
// of type aliases are eagerly resolved.
function isAliasedType(node: Node): boolean {
function isResolvedByTypeAlias(node: Node): boolean {
const parent = node.parent;
switch (parent.kind) {
case SyntaxKind.ParenthesizedType:
Expand All @@ -11565,21 +11574,52 @@ namespace ts {
case SyntaxKind.IndexedAccessType:
case SyntaxKind.ConditionalType:
case SyntaxKind.TypeOperator:
return isAliasedType(parent);
return isResolvedByTypeAlias(parent);
case SyntaxKind.TypeAliasDeclaration:
return true;
}
return false;
}

// Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution
// of a type alias.
function mayResolveTypeAlias(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.TypeReference:
return isJSDocTypeReference(node) || !!(resolveTypeReferenceName((<TypeReferenceNode>node).typeName, SymbolFlags.Type).flags & SymbolFlags.TypeAlias);
case SyntaxKind.TypeQuery:
return true;
case SyntaxKind.TypeOperator:
return (<TypeOperatorNode>node).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((<TypeOperatorNode>node).type);
case SyntaxKind.ParenthesizedType:
case SyntaxKind.OptionalType:
case SyntaxKind.JSDocOptionalType:
case SyntaxKind.JSDocNullableType:
case SyntaxKind.JSDocNonNullableType:
case SyntaxKind.JSDocTypeExpression:
return mayResolveTypeAlias((<ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode>node).type);
case SyntaxKind.RestType:
return (<RestTypeNode>node).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias((<ArrayTypeNode>(<RestTypeNode>node).type).elementType);
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
return some((<UnionOrIntersectionTypeNode>node).types, mayResolveTypeAlias);
case SyntaxKind.IndexedAccessType:
return mayResolveTypeAlias((<IndexedAccessTypeNode>node).objectType) || mayResolveTypeAlias((<IndexedAccessTypeNode>node).indexType);
case SyntaxKind.ConditionalType:
return mayResolveTypeAlias((<ConditionalTypeNode>node).checkType) || mayResolveTypeAlias((<ConditionalTypeNode>node).extendsType) ||
mayResolveTypeAlias((<ConditionalTypeNode>node).trueType) || mayResolveTypeAlias((<ConditionalTypeNode>node).falseType);
}
return false;
}

function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type {
const links = getNodeLinks(node);
if (!links.resolvedType) {
const target = getArrayOrTupleTargetType(node);
if (target === emptyGenericType) {
links.resolvedType = emptyObjectType;
}
else if (isAliasedType(node)) {
else if (isDeferredTypeReferenceNode(node)) {
links.resolvedType = node.kind === SyntaxKind.TupleType && node.elementTypes.length === 0 ? target :
createDeferredTypeReference(target, node, /*mapper*/ undefined);
}
Expand Down Expand Up @@ -12902,7 +12942,7 @@ namespace ts {
return links.resolvedType;
}

function getAliasSymbolForTypeNode(node: TypeNode) {
function getAliasSymbolForTypeNode(node: Node) {
let host = node.parent;
while (isParenthesizedTypeNode(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) {
host = host.parent;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/checkJsxChildrenProperty3.types
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class FetchUser extends React.Component<IFetchUserProps, any> {

? this.props.children(this.state.result)
>this.props.children(this.state.result) : JSX.Element
>this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement<any> | any[])[])
>this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement<any>)[])
>this.props : IFetchUserProps & { children?: React.ReactNode; }
>this : this
>props : IFetchUserProps & { children?: React.ReactNode; }
>children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement<any> | any[])[])
>children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement<any>)[])
>this.state.result : any
>this.state : any
>this : this
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/checkJsxChildrenProperty4.errors.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
tests/cases/conformance/jsx/file.tsx(24,28): error TS2551: Property 'NAme' does not exist on type 'IUser'. Did you mean 'Name'?
tests/cases/conformance/jsx/file.tsx(36,15): error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | ReactElement<any> | any[]'.
Type '(user: IUser) => Element' is missing the following properties from type 'any[]': push, pop, concat, join, and 15 more.
tests/cases/conformance/jsx/file.tsx(39,15): error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | ReactElement<any> | any[]'.
Type '(user: IUser) => Element' is missing the following properties from type 'any[]': push, pop, concat, join, and 15 more.
tests/cases/conformance/jsx/file.tsx(36,15): error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
tests/cases/conformance/jsx/file.tsx(39,15): error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props


==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
Expand Down Expand Up @@ -50,17 +50,17 @@ tests/cases/conformance/jsx/file.tsx(39,15): error TS2322: Type '(user: IUser) =
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
) }
~~~~~~~~~~~~~
!!! error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | ReactElement<any> | any[]'.
!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'any[]': push, pop, concat, join, and 15 more.
!!! error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
!!! related TS6212 tests/cases/conformance/jsx/file.tsx:36:15: Did you mean to call this expression?
{ user => (
~~~~~~~~~
<h1>{ user.Name }</h1>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
) }
~~~~~~~~~~~~~
!!! error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | ReactElement<any> | any[]'.
!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'any[]': push, pop, concat, join, and 15 more.
!!! error TS2322: Type '(user: IUser) => Element' is not assignable to type 'string | number | boolean | any[] | ReactElement<any>'.
!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement<any>': type, props
!!! related TS6212 tests/cases/conformance/jsx/file.tsx:39:15: Did you mean to call this expression?
</FetchUser>
);
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/checkJsxChildrenProperty4.types
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class FetchUser extends React.Component<IFetchUserProps, any> {

? this.props.children(this.state.result)
>this.props.children(this.state.result) : JSX.Element
>this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement<any> | any[])[])
>this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement<any>)[])
>this.props : IFetchUserProps & { children?: React.ReactNode; }
>this : this
>props : IFetchUserProps & { children?: React.ReactNode; }
>children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement<any> | any[])[])
>children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement<any>) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement<any>)[])
>this.state.result : any
>this.state : any
>this : this
Expand Down

0 comments on commit de37c87

Please sign in to comment.