Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17699,10 +17699,11 @@ func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Ty
t = c.getTypeFromTypeNode(declaration.Type())
break
}
assignedType := c.getAssignmentDeclarationInitializerType(declaration)
// We ignore initial assignments of undefined to CommonJS exports when there are multiple assignment declarations
if ast.GetAssignmentDeclarationKind(declaration) != ast.JSDeclarationKindExportsProperty || i != 0 || len(symbol.Declarations) == 1 || assignedType.flags&TypeFlagsUndefined == 0 {
types = core.AppendIfUnique(types, assignedType)
if assignedType := c.getAssignmentDeclarationInitializerType(declaration); assignedType != nil {
// We ignore initial assignments of undefined to CommonJS exports when there are multiple assignment declarations
if ast.GetAssignmentDeclarationKind(declaration) != ast.JSDeclarationKindExportsProperty || i != 0 || len(symbol.Declarations) == 1 || assignedType.flags&TypeFlagsUndefined == 0 {
types = core.AppendIfUnique(types, assignedType)
}
}
}
if kind == thisAssignmentDeclarationMethod && len(types) > 0 {
Expand All @@ -17711,7 +17712,10 @@ func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Ty
}
}
if t == nil {
t = c.getUnionType(types)
t = c.anyType
if len(types) != 0 {
t = c.getUnionType(types)
}
}
}
t = c.getWidenedType(t)
Expand All @@ -17726,16 +17730,42 @@ func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Ty

func (c *Checker) getAssignmentDeclarationInitializerType(node *ast.Node) *Type {
if ast.IsBinaryExpression(node) {
var t *Type
switch ast.GetAssignmentDeclarationKind(node) {
case ast.JSDeclarationKindModuleExports, ast.JSDeclarationKindExportsProperty:
return c.getRegularTypeOfLiteralType(c.checkExpressionCached(ast.GetRightMostAssignedExpression(node)))
t = c.getRegularTypeOfLiteralType(c.checkExpressionCached(ast.GetRightMostAssignedExpression(node)))
case ast.JSDeclarationKindThisProperty:
if c.containsSameNamedThisProperty(node.AsBinaryExpression().Left, node.AsBinaryExpression().Right) {
return nil
}
fallthrough
default:
t = c.checkExpressionForMutableLocation(node.AsBinaryExpression().Right, CheckModeNormal)
}
if c.isEmptyArrayLiteralType(t) {
c.reportImplicitAny(node, c.anyArrayType, WideningKindNormal)
return c.anyArrayType
}
return c.checkExpressionForMutableLocation(node.AsBinaryExpression().Right, CheckModeNormal)
return t
}
if ast.IsCallExpression(node) {
return c.getTypeFromPropertyDescriptor(node.Arguments()[2])
}
return c.neverType
return nil
}

func (c *Checker) containsSameNamedThisProperty(thisProperty *ast.Node, expression *ast.Node) bool {
var visit func(node *ast.Node) bool
visit = func(node *ast.Node) bool {
if c.isMatchingReference(thisProperty, node) {
return true
}
if ast.IsFunctionLike(node) {
return false
}
return node.ForEachChild(visit)
Comment on lines +17763 to +17766
}
return visit(expression)
}

func (c *Checker) getTypeFromPropertyDescriptor(node *ast.Node) *Type {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
main.js(47,21): error TS2532: Object is possibly 'undefined'.
main.js(55,9): error TS2322: Type 'string' is not assignable to type 'number'.
main.js(64,9): error TS7008: Member 'bar' implicitly has an 'any[]' type.


==== main.js (3 errors) ====
class C1 {
constructor() {
this.foo = [3];
this.foo = [this.foo[0] * 2];
this.foo; // number[]
}
}

class C2 {
constructor() {
this.foo = [3];
this.foo; // number[]
}
method1() {
this.foo = [this.foo[0] * 2];
}
}

class C4 {
constructor() {
this.bar = [];
this.bar.push("baz");
this.bar; // string[]
}
}

// When this.xxx assignment declarations occur only in methods, the declared type of the
// property is a union of the types of all assigned values, excluding values from self-referential
// assignments, plus undefined. Local CFA removes the undefined when possible.

class C5 {
method1() {
this.foo; // number[] | undefined
this.foo = [3]
this.foo = [this.foo[0] * 2]
this.foo; // number[]
}
}

class C6 {
method1() {
this.foo = [3];
this.foo; // number[]
}
method2() {
this.foo; // number[] | undefined
this.foo = [this.foo[0] * 2]; // Error: Object is possibly undefined
~~~~~~~~
!!! error TS2532: Object is possibly 'undefined'.
this.foo; // number[]
}
}

class C7 {
method1() {
this.foo = 0;
this.foo = this.foo + "abc"; // Error, 'string' not assignable to 'number'
~~~~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'number'.
}
}

// When this.xxx assigmment declarations occur only in methods, an assigned empty array
// literal value is given type 'any[]'.

class C8 {
method1() {
this.bar = []; // Error: Implicit any[]
~~~~~~~~~~~~~
!!! error TS7008: Member 'bar' implicitly has an 'any[]' type.
this.bar.push("baz");
this.bar; // any[]
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//// [tests/cases/compiler/thisPropertyAssignmentTyping.ts] ////

=== main.js ===
class C1 {
>C1 : Symbol(C1, Decl(main.js, 0, 0))

constructor() {
this.foo = [3];
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
>this : Symbol(C1, Decl(main.js, 0, 0))
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))

this.foo = [this.foo[0] * 2];
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
>this : Symbol(C1, Decl(main.js, 0, 0))
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
>this : Symbol(C1, Decl(main.js, 0, 0))
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))

this.foo; // number[]
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
>this : Symbol(C1, Decl(main.js, 0, 0))
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
}
}

class C2 {
>C2 : Symbol(C2, Decl(main.js, 6, 1))

constructor() {
this.foo = [3];
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
>this : Symbol(C2, Decl(main.js, 6, 1))
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))

this.foo; // number[]
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
>this : Symbol(C2, Decl(main.js, 6, 1))
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
}
method1() {
>method1 : Symbol(C2.method1, Decl(main.js, 12, 5))

this.foo = [this.foo[0] * 2];
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
>this : Symbol(C2, Decl(main.js, 6, 1))
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
>this : Symbol(C2, Decl(main.js, 6, 1))
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
}
}

class C4 {
>C4 : Symbol(C4, Decl(main.js, 16, 1))

constructor() {
this.bar = [];
>this.bar : Symbol(C4.bar, Decl(main.js, 19, 19))
>this : Symbol(C4, Decl(main.js, 16, 1))
>bar : Symbol(C4.bar, Decl(main.js, 19, 19))

this.bar.push("baz");
>this.bar.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>this.bar : Symbol(C4.bar, Decl(main.js, 19, 19))
>this : Symbol(C4, Decl(main.js, 16, 1))
>bar : Symbol(C4.bar, Decl(main.js, 19, 19))
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))

this.bar; // string[]
>this.bar : Symbol(C4.bar, Decl(main.js, 19, 19))
>this : Symbol(C4, Decl(main.js, 16, 1))
>bar : Symbol(C4.bar, Decl(main.js, 19, 19))
}
}

// When this.xxx assignment declarations occur only in methods, the declared type of the
// property is a union of the types of all assigned values, excluding values from self-referential
// assignments, plus undefined. Local CFA removes the undefined when possible.

class C5 {
>C5 : Symbol(C5, Decl(main.js, 24, 1))

method1() {
>method1 : Symbol(C5.method1, Decl(main.js, 30, 10))

this.foo; // number[] | undefined
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
>this : Symbol(C5, Decl(main.js, 24, 1))
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))

this.foo = [3]
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
>this : Symbol(C5, Decl(main.js, 24, 1))
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))

this.foo = [this.foo[0] * 2]
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
>this : Symbol(C5, Decl(main.js, 24, 1))
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
>this : Symbol(C5, Decl(main.js, 24, 1))
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))

this.foo; // number[]
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
>this : Symbol(C5, Decl(main.js, 24, 1))
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
}
}

class C6 {
>C6 : Symbol(C6, Decl(main.js, 37, 1))

method1() {
>method1 : Symbol(C6.method1, Decl(main.js, 39, 10))

this.foo = [3];
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this : Symbol(C6, Decl(main.js, 37, 1))
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))

this.foo; // number[]
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this : Symbol(C6, Decl(main.js, 37, 1))
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
}
method2() {
>method2 : Symbol(C6.method2, Decl(main.js, 43, 5))

this.foo; // number[] | undefined
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this : Symbol(C6, Decl(main.js, 37, 1))
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))

this.foo = [this.foo[0] * 2]; // Error: Object is possibly undefined
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this : Symbol(C6, Decl(main.js, 37, 1))
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this : Symbol(C6, Decl(main.js, 37, 1))
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))

this.foo; // number[]
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
>this : Symbol(C6, Decl(main.js, 37, 1))
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
}
}

class C7 {
>C7 : Symbol(C7, Decl(main.js, 49, 1))

method1() {
>method1 : Symbol(C7.method1, Decl(main.js, 51, 10))

this.foo = 0;
>this.foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
>this : Symbol(C7, Decl(main.js, 49, 1))
>foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))

this.foo = this.foo + "abc"; // Error, 'string' not assignable to 'number'
>this.foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
>this : Symbol(C7, Decl(main.js, 49, 1))
>foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
>this.foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
>this : Symbol(C7, Decl(main.js, 49, 1))
>foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
}
}

// When this.xxx assigmment declarations occur only in methods, an assigned empty array
// literal value is given type 'any[]'.

class C8 {
>C8 : Symbol(C8, Decl(main.js, 56, 1))

method1() {
>method1 : Symbol(C8.method1, Decl(main.js, 61, 10))

this.bar = []; // Error: Implicit any[]
>this.bar : Symbol(C8.bar, Decl(main.js, 62, 15))
>this : Symbol(C8, Decl(main.js, 56, 1))
>bar : Symbol(C8.bar, Decl(main.js, 62, 15))

this.bar.push("baz");
>this.bar.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
>this.bar : Symbol(C8.bar, Decl(main.js, 62, 15))
>this : Symbol(C8, Decl(main.js, 56, 1))
>bar : Symbol(C8.bar, Decl(main.js, 62, 15))
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))

this.bar; // any[]
>this.bar : Symbol(C8.bar, Decl(main.js, 62, 15))
>this : Symbol(C8, Decl(main.js, 56, 1))
>bar : Symbol(C8.bar, Decl(main.js, 62, 15))
}
}

Loading
Loading