diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7e0391c83ee1d..54788091d6b75 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11090,7 +11090,7 @@ namespace ts { return errorType; } } - if (node.kind === SyntaxKind.TypeReference && isAliasedType(node)) { + if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(node, length(node.typeArguments) !== typeParameters.length)) { return createDeferredTypeReference(type, node, /*mapper*/ undefined); } // In a type reference, the outer type parameters of the referenced class or interface are automatically @@ -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: @@ -11565,13 +11574,44 @@ 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((node).typeName, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); + case SyntaxKind.TypeQuery: + return true; + case SyntaxKind.TypeOperator: + return (node).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node).type); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.JSDocOptionalType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocTypeExpression: + return mayResolveTypeAlias((node).type); + case SyntaxKind.RestType: + return (node).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node).type).elementType); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return some((node).types, mayResolveTypeAlias); + case SyntaxKind.IndexedAccessType: + return mayResolveTypeAlias((node).objectType) || mayResolveTypeAlias((node).indexType); + case SyntaxKind.ConditionalType: + return mayResolveTypeAlias((node).checkType) || mayResolveTypeAlias((node).extendsType) || + mayResolveTypeAlias((node).trueType) || mayResolveTypeAlias((node).falseType); + } + return false; + } + function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -11579,7 +11619,7 @@ namespace ts { 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); } @@ -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; diff --git a/tests/baselines/reference/checkJsxChildrenProperty3.types b/tests/baselines/reference/checkJsxChildrenProperty3.types index 2d4380a197531..f2ed3dc8fd00a 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty3.types +++ b/tests/baselines/reference/checkJsxChildrenProperty3.types @@ -31,11 +31,11 @@ class FetchUser extends React.Component { ? 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) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement | 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) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >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) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement | 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) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >this.state.result : any >this.state : any >this : this diff --git a/tests/baselines/reference/checkJsxChildrenProperty4.errors.txt b/tests/baselines/reference/checkJsxChildrenProperty4.errors.txt index 40eb1d77677f5..34bd218996483 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty4.errors.txt +++ b/tests/baselines/reference/checkJsxChildrenProperty4.errors.txt @@ -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[]'. - 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[]'. - 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'. + Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement': 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'. + Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement': type, props ==== tests/cases/conformance/jsx/file.tsx (3 errors) ==== @@ -50,8 +50,8 @@ 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[]'. -!!! 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'. +!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement': type, props !!! related TS6212 tests/cases/conformance/jsx/file.tsx:36:15: Did you mean to call this expression? { user => ( ~~~~~~~~~ @@ -59,8 +59,8 @@ 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[]'. -!!! 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'. +!!! error TS2322: Type '(user: IUser) => Element' is missing the following properties from type 'ReactElement': type, props !!! related TS6212 tests/cases/conformance/jsx/file.tsx:39:15: Did you mean to call this expression? ); diff --git a/tests/baselines/reference/checkJsxChildrenProperty4.types b/tests/baselines/reference/checkJsxChildrenProperty4.types index 4f753cf9f3b03..067e8f2611af6 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty4.types +++ b/tests/baselines/reference/checkJsxChildrenProperty4.types @@ -31,11 +31,11 @@ class FetchUser extends React.Component { ? 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) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement | 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) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >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) | (((user: IUser) => JSX.Element) & (string | number | boolean | React.ReactElement | 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) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >this.state.result : any >this.state : any >this : this