Skip to content

Commit 7f3ffe1

Browse files
authored
Add this expandos (microsoft#860)
1 parent a3ce51b commit 7f3ffe1

File tree

521 files changed

+3729
-5824
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

521 files changed

+3729
-5824
lines changed

internal/ast/utilities.go

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,8 +1353,10 @@ func GetNonAssignedNameOfDeclaration(declaration *Node) *Node {
13531353
// !!!
13541354
switch declaration.Kind {
13551355
case KindBinaryExpression:
1356-
if IsFunctionPropertyAssignment(declaration) {
1357-
return GetElementOrPropertyAccessArgumentExpressionOrName(declaration.AsBinaryExpression().Left)
1356+
bin := declaration.AsBinaryExpression()
1357+
kind := GetAssignmentDeclarationKind(bin)
1358+
if kind == JSDeclarationKindProperty || kind == JSDeclarationKindThisProperty {
1359+
return GetElementOrPropertyAccessArgumentExpressionOrName(bin.Left)
13581360
}
13591361
return nil
13601362
case KindExportAssignment, KindJSExportAssignment:
@@ -1400,21 +1402,46 @@ func getAssignedName(node *Node) *Node {
14001402
return nil
14011403
}
14021404

1403-
func IsFunctionPropertyAssignment(node *Node) bool {
1404-
if node.Kind == KindBinaryExpression {
1405-
expr := node.AsBinaryExpression()
1406-
if expr.OperatorToken.Kind == KindEqualsToken {
1407-
switch expr.Left.Kind {
1408-
case KindPropertyAccessExpression:
1409-
// F.id = expr
1410-
return IsIdentifier(expr.Left.Expression()) && IsIdentifier(expr.Left.Name())
1411-
case KindElementAccessExpression:
1412-
// F[xxx] = expr
1413-
return IsIdentifier(expr.Left.Expression())
1414-
}
1415-
}
1405+
type JSDeclarationKind int
1406+
1407+
const (
1408+
JSDeclarationKindNone JSDeclarationKind = iota
1409+
/// module.exports = expr
1410+
JSDeclarationKindModuleExports
1411+
/// exports.name = expr
1412+
/// module.exports.name = expr
1413+
JSDeclarationKindExportsProperty
1414+
/// className.prototype.name = expr
1415+
JSDeclarationKindPrototypeProperty
1416+
/// this.name = expr
1417+
JSDeclarationKindThisProperty
1418+
/// F.name = expr, F[name] = expr
1419+
JSDeclarationKindProperty
1420+
)
1421+
1422+
func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind {
1423+
if bin.OperatorToken.Kind != KindEqualsToken || !IsAccessExpression(bin.Left) {
1424+
return JSDeclarationKindNone
14161425
}
1417-
return false
1426+
if IsModuleExportsAccessExpression(bin.Left) {
1427+
return JSDeclarationKindModuleExports
1428+
} else if (IsModuleExportsAccessExpression(bin.Left.Expression()) || IsExportsIdentifier(bin.Left.Expression())) &&
1429+
hasJSBindableName(bin.Left) {
1430+
return JSDeclarationKindExportsProperty
1431+
}
1432+
if bin.Left.Expression().Kind == KindThisKeyword {
1433+
return JSDeclarationKindThisProperty
1434+
}
1435+
if bin.Left.Kind == KindPropertyAccessExpression && IsIdentifier(bin.Left.Expression()) && hasJSBindableName(bin.Left) ||
1436+
bin.Left.Kind == KindElementAccessExpression && IsIdentifier(bin.Left.Expression()) {
1437+
return JSDeclarationKindProperty
1438+
}
1439+
return JSDeclarationKindNone
1440+
}
1441+
1442+
func hasJSBindableName(node *Node) bool {
1443+
name := GetElementOrPropertyAccessArgumentExpressionOrName(node)
1444+
return IsIdentifier(name) || IsStringLiteralLike(name)
14181445
}
14191446

14201447
/**

internal/binder/binder.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,8 +622,11 @@ func (b *Binder) bind(node *ast.Node) bool {
622622
setFlowNode(node, b.currentFlow)
623623
}
624624
case ast.KindBinaryExpression:
625-
if ast.IsFunctionPropertyAssignment(node) {
625+
switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) {
626+
case ast.JSDeclarationKindProperty:
626627
b.bindFunctionPropertyAssignment(node)
628+
case ast.JSDeclarationKindThisProperty:
629+
b.bindThisPropertyAssignment(node)
627630
}
628631
b.checkStrictModeBinaryExpression(node)
629632
case ast.KindCatchClause:
@@ -1042,6 +1045,41 @@ func (b *Binder) bindFunctionPropertyAssignment(node *ast.Node) {
10421045
}
10431046
}
10441047

1048+
func (b *Binder) bindThisPropertyAssignment(node *ast.Node) {
1049+
if !ast.IsInJSFile(node) {
1050+
return
1051+
}
1052+
bin := node.AsBinaryExpression()
1053+
if ast.IsPropertyAccessExpression(bin.Left) && ast.IsPrivateIdentifier(bin.Left.AsPropertyAccessExpression().Name()) {
1054+
return
1055+
}
1056+
thisContainer := ast.GetThisContainer(node /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/, false)
1057+
switch thisContainer.Kind {
1058+
case ast.KindFunctionDeclaration, ast.KindFunctionExpression:
1059+
// !!! constructor functions
1060+
case ast.KindConstructor, ast.KindPropertyDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindClassStaticBlockDeclaration:
1061+
// this.property assignment in class member -- bind to the containing class
1062+
containingClass := thisContainer.Parent
1063+
classSymbol := containingClass.Symbol()
1064+
var symbolTable ast.SymbolTable
1065+
if ast.IsStatic(thisContainer) {
1066+
symbolTable = ast.GetExports(containingClass.Symbol())
1067+
} else {
1068+
symbolTable = ast.GetMembers(containingClass.Symbol())
1069+
}
1070+
if ast.HasDynamicName(node) {
1071+
b.declareSymbolEx(symbolTable, containingClass.Symbol(), node, ast.SymbolFlagsProperty, ast.SymbolFlagsNone, true /*isReplaceableByMethod*/, true /*isComputedName*/)
1072+
addLateBoundAssignmentDeclarationToSymbol(node, classSymbol)
1073+
} else {
1074+
b.declareSymbolEx(symbolTable, containingClass.Symbol(), node, ast.SymbolFlagsProperty|ast.SymbolFlagsAssignment, ast.SymbolFlagsNone, true /*isReplaceableByMethod*/, false /*isComputedName*/)
1075+
}
1076+
case ast.KindSourceFile, ast.KindModuleDeclaration:
1077+
// top-level this.property as assignment to globals is no longer supported
1078+
default:
1079+
panic("Unhandled case in bindThisPropertyAssignment: " + thisContainer.Kind.String())
1080+
}
1081+
}
1082+
10451083
func (b *Binder) bindEnumDeclaration(node *ast.Node) {
10461084
if ast.IsEnumConst(node) {
10471085
b.bindBlockScopedDeclaration(node, ast.SymbolFlagsConstEnum, ast.SymbolFlagsConstEnumExcludes)

internal/checker/checker.go

Lines changed: 138 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ type Checker struct {
581581
templateLiteralTypes map[string]*Type
582582
stringMappingTypes map[StringMappingKey]*Type
583583
uniqueESSymbolTypes map[*ast.Symbol]*Type
584+
thisExpandoKinds map[*ast.Symbol]thisAssignmentDeclarationKind
585+
thisExpandoLocations map[*ast.Symbol]*ast.Node
584586
subtypeReductionCache map[string][]*Type
585587
cachedTypes map[CachedTypeKey]*Type
586588
cachedSignatures map[CachedSignatureKey]*Signature
@@ -866,6 +868,8 @@ func NewChecker(program Program) *Checker {
866868
c.templateLiteralTypes = make(map[string]*Type)
867869
c.stringMappingTypes = make(map[StringMappingKey]*Type)
868870
c.uniqueESSymbolTypes = make(map[*ast.Symbol]*Type)
871+
c.thisExpandoKinds = make(map[*ast.Symbol]thisAssignmentDeclarationKind)
872+
c.thisExpandoLocations = make(map[*ast.Symbol]*ast.Node)
869873
c.subtypeReductionCache = make(map[string][]*Type)
870874
c.cachedTypes = make(map[CachedTypeKey]*Type)
871875
c.cachedSignatures = make(map[CachedSignatureKey]*Signature)
@@ -10809,9 +10813,9 @@ func (c *Checker) getFlowTypeOfProperty(reference *ast.Node, prop *ast.Symbol) *
1080910813
func (c *Checker) getTypeOfPropertyInBaseClass(property *ast.Symbol) *Type {
1081010814
classType := c.getDeclaringClass(property)
1081110815
if classType != nil {
10812-
baseClassType := c.getBaseTypes(classType)[0]
10813-
if baseClassType != nil {
10814-
return c.getTypeOfPropertyOfType(baseClassType, property.Name)
10816+
baseClassTypes := c.getBaseTypes(classType)
10817+
if len(baseClassTypes) > 0 {
10818+
return c.getTypeOfPropertyOfType(baseClassTypes[0], property.Name)
1081510819
}
1081610820
}
1081710821
return nil
@@ -16710,14 +16714,101 @@ func (c *Checker) getTypeOfPrototypeProperty(prototype *ast.Symbol) *Type {
1671016714
return classType
1671116715
}
1671216716

16717+
type thisAssignmentDeclarationKind int32
16718+
16719+
const (
16720+
thisAssignmentDeclarationNone thisAssignmentDeclarationKind = iota // not (all) this.property assignments
16721+
thisAssignmentDeclarationTyped // typed; use the type annotation
16722+
thisAssignmentDeclarationConstructor // at least one in the constructor; use control flow
16723+
thisAssignmentDeclarationMethod // methods only; look in base first, and if not found, union all declaration types plus undefined
16724+
)
16725+
1671316726
func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Type {
16714-
var types []*Type
16727+
var t *Type
16728+
kind, location := c.isConstructorDeclaredThisProperty(symbol)
16729+
if kind == thisAssignmentDeclarationTyped {
16730+
if location == nil {
16731+
panic("location should not be nil when this assignment has a type.")
16732+
}
16733+
t = c.getTypeFromTypeNode(location)
16734+
} else if kind == thisAssignmentDeclarationConstructor {
16735+
if location == nil {
16736+
panic("constructor should not be nil when this assignment is in a constructor.")
16737+
}
16738+
t = c.getFlowTypeInConstructor(symbol, location)
16739+
} else if kind == thisAssignmentDeclarationMethod {
16740+
t = c.getTypeOfPropertyInBaseClass(symbol)
16741+
}
16742+
if t == nil {
16743+
var types []*Type
16744+
for _, declaration := range symbol.Declarations {
16745+
if ast.IsBinaryExpression(declaration) {
16746+
types = core.AppendIfUnique(types, c.checkExpressionForMutableLocation(declaration.AsBinaryExpression().Right, CheckModeNormal))
16747+
}
16748+
}
16749+
if kind == thisAssignmentDeclarationMethod && len(types) > 0 {
16750+
if c.strictNullChecks {
16751+
types = core.AppendIfUnique(types, c.undefinedOrMissingType)
16752+
}
16753+
}
16754+
t = c.getWidenedType(c.getUnionType(types))
16755+
}
16756+
// report an all-nullable or empty union as an implicit any in JS files
16757+
if symbol.ValueDeclaration != nil && ast.IsInJSFile(symbol.ValueDeclaration) &&
16758+
c.filterType(t, func(c *Type) bool { return c.Flags() & ^TypeFlagsNullable != 0 }) == c.neverType {
16759+
c.reportImplicitAny(symbol.ValueDeclaration, c.anyType, WideningKindNormal)
16760+
return c.anyType
16761+
}
16762+
return t
16763+
}
16764+
16765+
// A property is considered a constructor declared property when all declaration sites are this.xxx assignments,
16766+
// when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of
16767+
// a class constructor.
16768+
func (c *Checker) isConstructorDeclaredThisProperty(symbol *ast.Symbol) (thisAssignmentDeclarationKind, *ast.Node) {
16769+
if symbol.ValueDeclaration == nil || !ast.IsBinaryExpression(symbol.ValueDeclaration) {
16770+
return thisAssignmentDeclarationNone, nil
16771+
}
16772+
if kind, ok := c.thisExpandoKinds[symbol]; ok {
16773+
location, ok2 := c.thisExpandoLocations[symbol]
16774+
if !ok2 {
16775+
panic("ctor should be cached whenever this expando location is cached")
16776+
}
16777+
return kind, location
16778+
}
16779+
allThis := true
16780+
var typeAnnotation *ast.Node
1671516781
for _, declaration := range symbol.Declarations {
16716-
if ast.IsBinaryExpression(declaration) {
16717-
types = core.AppendIfUnique(types, c.checkExpressionForMutableLocation(declaration.AsBinaryExpression().Right, CheckModeNormal))
16782+
if !ast.IsBinaryExpression(declaration) {
16783+
allThis = false
16784+
break
16785+
}
16786+
bin := declaration.AsBinaryExpression()
16787+
if ast.GetAssignmentDeclarationKind(bin) == ast.JSDeclarationKindThisProperty &&
16788+
(bin.Left.Kind != ast.KindElementAccessExpression || ast.IsStringOrNumericLiteralLike(bin.Left.AsElementAccessExpression().ArgumentExpression)) {
16789+
// TODO: if bin.Type() != nil, use bin.Type()
16790+
if bin.Right.Kind == ast.KindTypeAssertionExpression {
16791+
typeAnnotation = bin.Right.AsTypeAssertion().Type
16792+
}
16793+
} else {
16794+
allThis = false
16795+
break
1671816796
}
1671916797
}
16720-
return c.getWidenedType(c.getUnionType(types))
16798+
var location *ast.Node
16799+
kind := thisAssignmentDeclarationNone
16800+
if allThis {
16801+
if typeAnnotation != nil {
16802+
location = typeAnnotation
16803+
kind = thisAssignmentDeclarationTyped
16804+
} else {
16805+
location = c.getDeclaringConstructor(symbol)
16806+
kind = core.IfElse(location == nil, thisAssignmentDeclarationMethod, thisAssignmentDeclarationConstructor)
16807+
}
16808+
}
16809+
c.thisExpandoKinds[symbol] = kind
16810+
c.thisExpandoLocations[symbol] = location
16811+
return kind, location
1672116812
}
1672216813

1672316814
func (c *Checker) widenTypeForVariableLikeDeclaration(t *Type, declaration *ast.Node, reportErrors bool) *Type {
@@ -27595,8 +27686,8 @@ func (c *Checker) getContextualTypeForBinaryOperand(node *ast.Node, contextFlags
2759527686
case ast.KindEqualsToken, ast.KindAmpersandAmpersandEqualsToken, ast.KindBarBarEqualsToken, ast.KindQuestionQuestionEqualsToken:
2759627687
// In an assignment expression, the right operand is contextually typed by the type of the left operand
2759727688
// unless it's an assignment declaration.
27598-
if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) && (binary.Symbol == nil || c.canGetContextualTypeForAssignmentDeclaration(binary.Left)) {
27599-
return c.getContextualTypeFromAssignmentTarget(binary.Left)
27689+
if node == binary.Right && !c.isReferenceToModuleExports(binary.Left) && (binary.Symbol == nil || c.canGetContextualTypeForAssignmentDeclaration(node.Parent)) {
27690+
return c.getTypeOfExpression(binary.Left)
2760027691
}
2760127692
case ast.KindBarBarToken, ast.KindQuestionQuestionToken:
2760227693
// When an || expression has a contextual type, the operands are contextually typed by that type, except
@@ -27622,47 +27713,60 @@ func (c *Checker) canGetContextualTypeForAssignmentDeclaration(node *ast.Node) b
2762227713
// binder) of the form 'F.id = expr' or 'F[xxx] = expr'. If 'F' is declared as a variable with a type annotation,
2762327714
// we can obtain a contextual type from the annotated type without triggering a circularity. Otherwise, the
2762427715
// assignment declaration has no contextual type.
27625-
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(node.Expression()))
27626-
return symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Type() != nil
27627-
}
27628-
27629-
func (c *Checker) isReferenceToModuleExports(node *ast.Node) bool {
27630-
if ast.IsAccessExpression(node) {
27631-
expr := node.Expression()
27632-
if ast.IsIdentifier(expr) {
27633-
// Node is the left operand of an assignment expression of the form 'module.exports = expr'.
27634-
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27635-
return symbol.Flags&ast.SymbolFlagsModuleExports != 0
27636-
}
27637-
}
27638-
return false
27639-
}
27640-
27641-
func (c *Checker) getContextualTypeFromAssignmentTarget(node *ast.Node) *Type {
27642-
if ast.IsAccessExpression(node) && node.Expression().Kind == ast.KindThisKeyword {
27716+
left := node.AsBinaryExpression().Left
27717+
expr := left.Expression()
27718+
if ast.IsAccessExpression(left) && expr.Kind == ast.KindThisKeyword {
2764327719
var symbol *ast.Symbol
27644-
if ast.IsPropertyAccessExpression(node) {
27645-
name := node.Name()
27646-
thisType := c.getTypeOfExpression(node.Expression())
27720+
if ast.IsPropertyAccessExpression(left) {
27721+
name := left.Name()
27722+
thisType := c.getTypeOfExpression(expr)
2764727723
if ast.IsPrivateIdentifier(name) {
2764827724
symbol = c.getPropertyOfType(thisType, binder.GetSymbolNameForPrivateIdentifier(thisType.symbol, name.Text()))
2764927725
} else {
2765027726
symbol = c.getPropertyOfType(thisType, name.Text())
2765127727
}
2765227728
} else {
27653-
propType := c.checkExpressionCached(node.AsElementAccessExpression().ArgumentExpression)
27729+
propType := c.checkExpressionCached(left.AsElementAccessExpression().ArgumentExpression)
2765427730
if isTypeUsableAsPropertyName(propType) {
27655-
symbol = c.getPropertyOfType(c.getTypeOfExpression(node.Expression()), getPropertyNameFromType(propType))
27731+
symbol = c.getPropertyOfType(c.getTypeOfExpression(expr), getPropertyNameFromType(propType))
2765627732
}
2765727733
}
2765827734
if symbol != nil {
2765927735
d := symbol.ValueDeclaration
2766027736
if d != nil && (ast.IsPropertyDeclaration(d) || ast.IsPropertySignatureDeclaration(d)) && d.Type() == nil && d.Initializer() == nil {
27661-
return nil
27737+
return false
27738+
}
27739+
}
27740+
symbol = node.Symbol()
27741+
if symbol != nil && symbol.ValueDeclaration != nil && symbol.ValueDeclaration.Type() == nil {
27742+
if !ast.IsObjectLiteralMethod(c.getThisContainer(expr, false, false)) {
27743+
return false
27744+
}
27745+
// and now for one single case of object literal methods
27746+
name := ast.GetElementOrPropertyAccessArgumentExpressionOrName(left)
27747+
if name == nil {
27748+
return false
27749+
} else {
27750+
// !!! contextual typing for `this` in object literals
27751+
return false
2766227752
}
2766327753
}
27754+
return true
2766427755
}
27665-
return c.getTypeOfExpression(node)
27756+
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27757+
return symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Type() != nil
27758+
}
27759+
27760+
func (c *Checker) isReferenceToModuleExports(node *ast.Node) bool {
27761+
if ast.IsAccessExpression(node) {
27762+
expr := node.Expression()
27763+
if ast.IsIdentifier(expr) {
27764+
// Node is the left operand of an assignment expression of the form 'module.exports = expr'.
27765+
symbol := c.getExportSymbolOfValueSymbolIfExported(c.getResolvedSymbol(expr))
27766+
return symbol.Flags&ast.SymbolFlagsModuleExports != 0
27767+
}
27768+
}
27769+
return false
2766627770
}
2766727771

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

internal/checker/types.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,10 @@ type ExportTypeLinks struct {
152152
// Links for type aliases
153153

154154
type TypeAliasLinks struct {
155-
declaredType *Type
156-
typeParameters []*Type // Type parameters of type alias (undefined if non-generic)
157-
instantiations map[string]*Type // Instantiations of generic type alias (undefined if non-generic)
155+
declaredType *Type
156+
typeParameters []*Type // Type parameters of type alias (undefined if non-generic)
157+
instantiations map[string]*Type // Instantiations of generic type alias (undefined if non-generic)
158+
isConstructorDeclaredProperty bool
158159
}
159160

160161
// Links for declared types (type parameters, class types, interface types, enums)

0 commit comments

Comments
 (0)