Skip to content

Commit 102852b

Browse files
authored
Simplify and fix logic for assignment expression contextual types (#962)
1 parent dc21070 commit 102852b

5 files changed

+66
-198
lines changed

internal/checker/checker.go

Lines changed: 58 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27727,11 +27727,8 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
2772727727
case ast.KindEqualsToken, ast.KindAmpersandAmpersandEqualsToken, ast.KindBarBarEqualsToken, ast.KindQuestionQuestionEqualsToken:
2772827728
// In an assignment expression, the right operand is contextually typed by the type of the left operand
2772927729
// unless it's an assignment declaration.
27730-
if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) {
27731-
if binary.Symbol != nil {
27732-
return c.getContextualTypeForAssignmentDeclaration(node.Parent)
27733-
}
27734-
return c.getTypeOfExpression(binary.Left)
27730+
if node == binary.Right {
27731+
return c.getContextualTypeForAssignmentExpression(binary)
2773527732
}
2773627733
case ast.KindBarBarToken, ast.KindQuestionQuestionToken:
2773727734
// When an || expression has a contextual type, the operands are contextually typed by that type, except
@@ -27752,78 +27749,73 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
2775227749
return nil
2775327750
}
2775427751

27755-
func (c *Checker) getContextualTypeForAssignmentDeclaration(node *ast.Node) *Type {
27756-
// Node is the left operand of an assignment declaration (a binary expression with a symbol assigned by the
27757-
// binder) of the form 'F.id = expr' or 'F[xxx] = expr'. If 'F' is declared as a variable with a type annotation,
27758-
// we can obtain a contextual type from the annotated type without triggering a circularity. Otherwise, the
27759-
// assignment declaration has no contextual type.
27760-
left := node.AsBinaryExpression().Left
27761-
expr := left.Expression()
27762-
switch expr.Kind {
27763-
case ast.KindThisKeyword:
27764-
var symbol *ast.Symbol
27765-
if ast.IsPropertyAccessExpression(left) {
27766-
name := left.Name()
27767-
thisType := c.getTypeOfExpression(expr)
27768-
if ast.IsPrivateIdentifier(name) {
27769-
symbol = c.getPropertyOfType(thisType, binder.GetSymbolNameForPrivateIdentifier(thisType.symbol, name.Text()))
27770-
} else {
27771-
symbol = c.getPropertyOfType(thisType, name.Text())
27772-
}
27773-
} else {
27774-
propType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
27775-
if isTypeUsableAsPropertyName(propType) {
27776-
symbol = c.getPropertyOfType(c.getTypeOfExpression(expr), getPropertyNameFromType(propType))
27777-
}
27778-
}
27779-
if symbol != nil {
27780-
d := symbol.ValueDeclaration
27781-
if d != nil && (ast.IsPropertyDeclaration(d) || ast.IsPropertySignatureDeclaration(d)) && d.Type() == nil && d.Initializer() == nil {
27752+
func (c *Checker) getContextualTypeForAssignmentExpression(binary *ast.BinaryExpression) *Type {
27753+
left := binary.Left
27754+
if ast.IsAccessExpression(left) {
27755+
expr := left.Expression()
27756+
switch expr.Kind {
27757+
case ast.KindIdentifier:
27758+
if symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr)); symbol.Flags&ast.SymbolFlagsModuleExports != 0 {
27759+
// No contextual type for an expression of the form 'module.exports = expr'.
2778227760
return nil
2778327761
}
27784-
}
27785-
symbol = node.Symbol()
27786-
if symbol != nil && symbol.ValueDeclaration != nil && symbol.ValueDeclaration.Type() == nil {
27787-
if !ast.IsObjectLiteralMethod(c.getThisContainer(expr, false, false)) {
27762+
if binary.Symbol != nil {
27763+
// We have an assignment declaration (a binary expression with a symbol assigned by the binder) of the form
27764+
// 'F.id = expr' or 'F[xxx] = expr'. If 'F' is declared as a variable with a type annotation, we can obtain a
27765+
// contextual type from the annotated type without triggering a circularity. Otherwise, the assignment
27766+
// declaration has no contextual type.
27767+
if symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr)); symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) {
27768+
if typeNode := symbol.ValueDeclaration.Type(); typeNode != nil {
27769+
if ast.IsPropertyAccessExpression(left) {
27770+
return c.getTypeOfPropertyOfContextualType(c.getTypeFromTypeNode(typeNode), left.Name().Text())
27771+
}
27772+
nameType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
27773+
if isTypeUsableAsPropertyName(nameType) {
27774+
return c.getTypeOfPropertyOfContextualTypeEx(c.getTypeFromTypeNode(typeNode), getPropertyNameFromType(nameType), nameType)
27775+
}
27776+
}
27777+
}
2778827778
return nil
2778927779
}
27790-
// and now for one single case of object literal methods
27791-
name := ast.GetElementOrPropertyAccessName(left)
27792-
if name == nil {
27793-
return nil
27780+
case ast.KindThisKeyword:
27781+
var symbol *ast.Symbol
27782+
if ast.IsPropertyAccessExpression(left) {
27783+
name := left.Name()
27784+
thisType := c.getTypeOfExpression(expr)
27785+
if ast.IsPrivateIdentifier(name) {
27786+
symbol = c.getPropertyOfType(thisType, binder.GetSymbolNameForPrivateIdentifier(thisType.symbol, name.Text()))
27787+
} else {
27788+
symbol = c.getPropertyOfType(thisType, name.Text())
27789+
}
2779427790
} else {
27795-
// !!! contextual typing for `this` in object literals
27796-
return nil
27791+
propType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
27792+
if isTypeUsableAsPropertyName(propType) {
27793+
symbol = c.getPropertyOfType(c.getTypeOfExpression(expr), getPropertyNameFromType(propType))
27794+
}
2779727795
}
27798-
}
27799-
return c.getTypeOfExpression(left)
27800-
case ast.KindIdentifier:
27801-
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27802-
if symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) {
27803-
if typeNode := symbol.ValueDeclaration.Type(); typeNode != nil {
27804-
if ast.IsPropertyAccessExpression(left) {
27805-
return c.getTypeOfPropertyOfContextualType(c.getTypeFromTypeNode(typeNode), left.Name().Text())
27796+
if symbol != nil {
27797+
if d := symbol.ValueDeclaration; d != nil && (ast.IsPropertyDeclaration(d) || ast.IsPropertySignatureDeclaration(d)) && d.Type() == nil && d.Initializer() == nil {
27798+
// No contextual type for 'this.xxx = expr', where xxx is declared as a property with no type annotation or initializer.
27799+
return nil
27800+
}
27801+
}
27802+
if binary.Symbol != nil && binary.Symbol.ValueDeclaration != nil && binary.Symbol.ValueDeclaration.Type() == nil {
27803+
// We have an assignment declaration 'this.xxx = expr' with no (synthetic) type annotation
27804+
if !ast.IsObjectLiteralMethod(c.getThisContainer(expr, false, false)) {
27805+
return nil
2780627806
}
27807-
nameType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
27808-
if isTypeUsableAsPropertyName(nameType) {
27809-
return c.getTypeOfPropertyOfContextualTypeEx(c.getTypeFromTypeNode(typeNode), getPropertyNameFromType(nameType), nameType)
27807+
// and now for one single case of object literal methods
27808+
name := ast.GetElementOrPropertyAccessName(left)
27809+
if name == nil {
27810+
return nil
27811+
} else {
27812+
// !!! contextual typing for `this` in object literals
27813+
return nil
2781027814
}
2781127815
}
2781227816
}
2781327817
}
27814-
return nil
27815-
}
27816-
27817-
func (c *Checker) isReferenceToModuleExports(node *ast.Node) bool {
27818-
if ast.IsAccessExpression(node) {
27819-
expr := node.Expression()
27820-
if ast.IsIdentifier(expr) {
27821-
// Node is the left operand of an assignment expression of the form 'module.exports = expr'.
27822-
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27823-
return symbol.Flags&ast.SymbolFlagsModuleExports != 0
27824-
}
27825-
}
27826-
return false
27818+
return c.getTypeOfExpression(left)
2782727819
}
2782827820

2782927821
func (c *Checker) getContextualTypeForObjectLiteralElement(element *ast.Node, contextFlags ContextFlags) *Type {

testdata/baselines/reference/submodule/compiler/classPropInitializationInferenceWithElementAccess.errors.txt

Lines changed: 0 additions & 31 deletions
This file was deleted.

testdata/baselines/reference/submodule/compiler/classPropInitializationInferenceWithElementAccess.errors.txt.diff

Lines changed: 0 additions & 35 deletions
This file was deleted.

testdata/baselines/reference/submodule/compiler/classPropInitializationInferenceWithElementAccess.types

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,47 @@ export class Cls {
66
>Cls : Cls
77

88
x;
9-
>x : any
9+
>x : number[]
1010

1111
y;
12-
>y : any
12+
>y : { seed: number; }
1313

1414
z;
15-
>z : any
15+
>z : string
1616

1717
0;
18-
>0 : any
18+
>0 : number[]
1919

2020
constructor(seed: number) {
2121
>seed : number
2222

2323
this['x'] = [seed];
2424
>this['x'] = [seed] : number[]
25-
>this['x'] : any
25+
>this['x'] : number[]
2626
>this : this
2727
>'x' : "x"
2828
>[seed] : number[]
2929
>seed : number
3030

3131
this['y'] = { seed };
3232
>this['y'] = { seed } : { seed: number; }
33-
>this['y'] : any
33+
>this['y'] : { seed: number; }
3434
>this : this
3535
>'y' : "y"
3636
>{ seed } : { seed: number; }
3737
>seed : number
3838

3939
this['z'] = `${seed}`;
4040
>this['z'] = `${seed}` : string
41-
>this['z'] : any
41+
>this['z'] : string
4242
>this : this
4343
>'z' : "z"
4444
>`${seed}` : string
4545
>seed : number
4646

4747
this[0] = [seed];
4848
>this[0] = [seed] : number[]
49-
>this[0] : any
49+
>this[0] : number[]
5050
>this : this
5151
>0 : 0
5252
>[seed] : number[]

testdata/baselines/reference/submodule/compiler/classPropInitializationInferenceWithElementAccess.types.diff

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)