Skip to content

Commit b0e1b84

Browse files
authored
Non-circular type resolution for expando assignment declarations (#940)
1 parent f9431be commit b0e1b84

File tree

5 files changed

+162
-10
lines changed

5 files changed

+162
-10
lines changed

internal/checker/checker.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27686,7 +27686,10 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
2768627686
case ast.KindEqualsToken, ast.KindAmpersandAmpersandEqualsToken, ast.KindBarBarEqualsToken, ast.KindQuestionQuestionEqualsToken:
2768727687
// In an assignment expression, the right operand is contextually typed by the type of the left operand
2768827688
// unless it's an assignment declaration.
27689-
if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) && (binary.Symbol == nil || c.canGetContextualTypeForAssignmentDeclaration(node.Parent)) {
27689+
if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) {
27690+
if binary.Symbol != nil {
27691+
return c.getContextualTypeForAssignmentDeclaration(node.Parent)
27692+
}
2769027693
return c.getTypeOfExpression(binary.Left)
2769127694
}
2769227695
case ast.KindBarBarToken, ast.KindQuestionQuestionToken:
@@ -27708,14 +27711,15 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
2770827711
return nil
2770927712
}
2771027713

27711-
func (c *Checker) canGetContextualTypeForAssignmentDeclaration(node *ast.Node) bool {
27714+
func (c *Checker) getContextualTypeForAssignmentDeclaration(node *ast.Node) *Type {
2771227715
// Node is the left operand of an assignment declaration (a binary expression with a symbol assigned by the
2771327716
// binder) of the form 'F.id = expr' or 'F[xxx] = expr'. If 'F' is declared as a variable with a type annotation,
2771427717
// we can obtain a contextual type from the annotated type without triggering a circularity. Otherwise, the
2771527718
// assignment declaration has no contextual type.
2771627719
left := node.AsBinaryExpression().Left
2771727720
expr := left.Expression()
27718-
if ast.IsAccessExpression(left) && expr.Kind == ast.KindThisKeyword {
27721+
switch expr.Kind {
27722+
case ast.KindThisKeyword:
2771927723
var symbol *ast.Symbol
2772027724
if ast.IsPropertyAccessExpression(left) {
2772127725
name := left.Name()
@@ -27734,27 +27738,39 @@ func (c *Checker) canGetContextualTypeForAssignmentDeclaration(node *ast.Node) b
2773427738
if symbol != nil {
2773527739
d := symbol.ValueDeclaration
2773627740
if d != nil && (ast.IsPropertyDeclaration(d) || ast.IsPropertySignatureDeclaration(d)) && d.Type() == nil && d.Initializer() == nil {
27737-
return false
27741+
return nil
2773827742
}
2773927743
}
2774027744
symbol = node.Symbol()
2774127745
if symbol != nil && symbol.ValueDeclaration != nil && symbol.ValueDeclaration.Type() == nil {
2774227746
if !ast.IsObjectLiteralMethod(c.getThisContainer(expr, false, false)) {
27743-
return false
27747+
return nil
2774427748
}
2774527749
// and now for one single case of object literal methods
2774627750
name := ast.GetElementOrPropertyAccessArgumentExpressionOrName(left)
2774727751
if name == nil {
27748-
return false
27752+
return nil
2774927753
} else {
2775027754
// !!! contextual typing for `this` in object literals
27751-
return false
27755+
return nil
27756+
}
27757+
}
27758+
return c.getTypeOfExpression(left)
27759+
case ast.KindIdentifier:
27760+
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27761+
if symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) {
27762+
if typeNode := symbol.ValueDeclaration.Type(); typeNode != nil {
27763+
if ast.IsPropertyAccessExpression(left) {
27764+
return c.getTypeOfPropertyOfContextualType(c.getTypeFromTypeNode(typeNode), left.Name().Text())
27765+
}
27766+
nameType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
27767+
if isTypeUsableAsPropertyName(nameType) {
27768+
return c.getTypeOfPropertyOfContextualTypeEx(c.getTypeFromTypeNode(typeNode), getPropertyNameFromType(nameType), nameType)
27769+
}
2775227770
}
2775327771
}
27754-
return true
2775527772
}
27756-
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27757-
return symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Type() != nil
27773+
return nil
2775827774
}
2775927775

2776027776
func (c *Checker) isReferenceToModuleExports(node *ast.Node) bool {

internal/testutil/harnessutil/harnessutil.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ func CompileFilesEx(
146146
}
147147
}
148148

149+
if includeLibDir {
150+
repo.SkipIfNoTypeScriptSubmodule(t)
151+
}
152+
149153
// !!!
150154
// ts.assign(options, ts.convertToOptionsWithAbsolutePaths(options, path => ts.getNormalizedAbsolutePath(path, currentDirectory)));
151155
if compilerOptions.OutDir != "" {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//// [tests/cases/compiler/expandoContextualTypes.tsx] ////
2+
3+
=== expandoContextualTypes.tsx ===
4+
// https://github.com/microsoft/typescript-go/issues/921
5+
6+
/// <reference path="react16.d.ts" />
7+
8+
import type { ComponentType } from "react";
9+
>ComponentType : Symbol(ComponentType, Decl(expandoContextualTypes.tsx, 4, 13))
10+
11+
export type Page<P = NonNullable<unknown>> = ComponentType<P> & {
12+
>Page : Symbol(Page, Decl(expandoContextualTypes.tsx, 4, 43))
13+
>P : Symbol(P, Decl(expandoContextualTypes.tsx, 6, 17))
14+
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
15+
>ComponentType : Symbol(ComponentType, Decl(expandoContextualTypes.tsx, 4, 13))
16+
>P : Symbol(P, Decl(expandoContextualTypes.tsx, 6, 17))
17+
18+
getLayout?: (component: JSX.Element) => JSX.Element;
19+
>getLayout : Symbol(getLayout, Decl(expandoContextualTypes.tsx, 6, 65))
20+
>component : Symbol(component, Decl(expandoContextualTypes.tsx, 7, 17))
21+
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12))
22+
>Element : Symbol(Element, Decl(react16.d.ts, 2494, 23))
23+
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12))
24+
>Element : Symbol(Element, Decl(react16.d.ts, 2494, 23))
25+
}
26+
27+
export const FooPage: Page = () => {
28+
>FooPage : Symbol(FooPage, Decl(expandoContextualTypes.tsx, 10, 12))
29+
>Page : Symbol(Page, Decl(expandoContextualTypes.tsx, 4, 43))
30+
31+
return (
32+
<div>
33+
>div : Symbol(div, Decl(react16.d.ts, 2546, 114))
34+
35+
<p>Foo</p>
36+
>p : Symbol(p, Decl(react16.d.ts, 2593, 102))
37+
>p : Symbol(p, Decl(react16.d.ts, 2593, 102))
38+
39+
</div>
40+
>div : Symbol(div, Decl(react16.d.ts, 2546, 114))
41+
42+
)
43+
};
44+
45+
FooPage.getLayout = () => {
46+
>FooPage.getLayout : Symbol(getLayout, Decl(expandoContextualTypes.tsx, 6, 65))
47+
>FooPage : Symbol(FooPage, Decl(expandoContextualTypes.tsx, 10, 12))
48+
>getLayout : Symbol(getLayout, Decl(expandoContextualTypes.tsx, 6, 65))
49+
50+
return <></>
51+
};
52+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//// [tests/cases/compiler/expandoContextualTypes.tsx] ////
2+
3+
=== expandoContextualTypes.tsx ===
4+
// https://github.com/microsoft/typescript-go/issues/921
5+
6+
/// <reference path="react16.d.ts" />
7+
8+
import type { ComponentType } from "react";
9+
>ComponentType : ComponentType<P>
10+
11+
export type Page<P = NonNullable<unknown>> = ComponentType<P> & {
12+
>Page : Page<P>
13+
14+
getLayout?: (component: JSX.Element) => JSX.Element;
15+
>getLayout : ((component: Element) => Element) | undefined
16+
>component : Element
17+
>JSX : any
18+
>JSX : any
19+
}
20+
21+
export const FooPage: Page = () => {
22+
>FooPage : Page<{}>
23+
>() => { return ( <div> <p>Foo</p> </div> )} : { (): Element; getLayout: () => Element; }
24+
25+
return (
26+
>( <div> <p>Foo</p> </div> ) : Element
27+
28+
<div>
29+
><div> <p>Foo</p> </div> : Element
30+
>div : any
31+
32+
<p>Foo</p>
33+
><p>Foo</p> : Element
34+
>p : any
35+
>p : any
36+
37+
</div>
38+
>div : any
39+
40+
)
41+
};
42+
43+
FooPage.getLayout = () => {
44+
>FooPage.getLayout = () => { return <></>} : () => Element
45+
>FooPage.getLayout : ((component: Element) => Element) | undefined
46+
>FooPage : StatelessComponent<{}> & { getLayout?: ((component: Element) => Element) | undefined; }
47+
>getLayout : ((component: Element) => Element) | undefined
48+
>() => { return <></>} : () => Element
49+
50+
return <></>
51+
><></> : Element
52+
53+
};
54+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @strict: true
2+
// @target: esnext
3+
// @noEmit: true
4+
// @jsx: preserve
5+
6+
// https://github.com/microsoft/typescript-go/issues/921
7+
8+
/// <reference path="/.lib/react16.d.ts" />
9+
10+
import type { ComponentType } from "react";
11+
12+
export type Page<P = NonNullable<unknown>> = ComponentType<P> & {
13+
getLayout?: (component: JSX.Element) => JSX.Element;
14+
}
15+
16+
export const FooPage: Page = () => {
17+
return (
18+
<div>
19+
<p>Foo</p>
20+
</div>
21+
)
22+
};
23+
24+
FooPage.getLayout = () => {
25+
return <></>
26+
};

0 commit comments

Comments
 (0)