Skip to content

Fix variable type inference circularity #14446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 4, 2017
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
10 changes: 6 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16131,7 +16131,7 @@ namespace ts {
}

function checkDeclarationInitializer(declaration: VariableLikeDeclaration) {
const type = checkExpressionCached(declaration.initializer);
const type = getTypeOfExpression(declaration.initializer, /*cache*/ true);
return getCombinedNodeFlags(declaration) & NodeFlags.Const ||
getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration) ||
isTypeAssertion(declaration.initializer) ? type : getWidenedLiteralType(type);
Expand Down Expand Up @@ -16204,10 +16204,12 @@ namespace ts {

// Returns the type of an expression. Unlike checkExpression, this function is simply concerned
// with computing the type and may not fully check all contained sub-expressions for errors.
function getTypeOfExpression(node: Expression) {
// A cache argument of true indicates that if the function performs a full type check, it is ok
// to cache the result.
function getTypeOfExpression(node: Expression, cache?: boolean) {
// Optimize for the common case of a call to a function with a single non-generic call
// signature where we can just fetch the return type without checking the arguments.
if (node.kind === SyntaxKind.CallExpression && (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword) {
if (node.kind === SyntaxKind.CallExpression && (<CallExpression>node).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(node, /*checkArgumentIsStringLiteral*/true)) {
const funcType = checkNonNullExpression((<CallExpression>node).expression);
const signature = getSingleCallSignature(funcType);
if (signature && !signature.typeParameters) {
Expand All @@ -16217,7 +16219,7 @@ namespace ts {
// Otherwise simply call checkExpression. Ideally, the entire family of checkXXX functions
// should have a parameter that indicates whether full error checking is required such that
// we can perform the optimizations locally.
return checkExpression(node);
return cache ? checkExpressionCached(node) : checkExpression(node);
}

// Checks an expression and returns its type. The contextualMapper parameter serves two purposes: When
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ambientRequireFunction.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

const fs = require("fs");
>fs : typeof "fs"
>require("fs") : any
>require("fs") : typeof "fs"
>require : (moduleName: string) => any
>"fs" : "fs"

Expand Down
44 changes: 44 additions & 0 deletions tests/baselines/reference/circularInferredTypeOfVariable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//// [circularInferredTypeOfVariable.ts]

// Repro from #14428

(async () => {
function foo(p: string[]): string[] {
return [];
}

function bar(p: string[]): string[] {
return [];
}

let a1: string[] | undefined = [];

while (true) {
let a2 = foo(a1!);
a1 = await bar(a2);
}
});

//// [circularInferredTypeOfVariable.js]
// Repro from #14428
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
(() => __awaiter(this, void 0, void 0, function* () {
function foo(p) {
return [];
}
function bar(p) {
return [];
}
let a1 = [];
while (true) {
let a2 = foo(a1);
a1 = yield bar(a2);
}
}));
34 changes: 34 additions & 0 deletions tests/baselines/reference/circularInferredTypeOfVariable.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
=== tests/cases/compiler/circularInferredTypeOfVariable.ts ===

// Repro from #14428

(async () => {
function foo(p: string[]): string[] {
>foo : Symbol(foo, Decl(circularInferredTypeOfVariable.ts, 3, 14))
>p : Symbol(p, Decl(circularInferredTypeOfVariable.ts, 4, 17))

return [];
}

function bar(p: string[]): string[] {
>bar : Symbol(bar, Decl(circularInferredTypeOfVariable.ts, 6, 5))
>p : Symbol(p, Decl(circularInferredTypeOfVariable.ts, 8, 17))

return [];
}

let a1: string[] | undefined = [];
>a1 : Symbol(a1, Decl(circularInferredTypeOfVariable.ts, 12, 7))

while (true) {
let a2 = foo(a1!);
>a2 : Symbol(a2, Decl(circularInferredTypeOfVariable.ts, 15, 11))
>foo : Symbol(foo, Decl(circularInferredTypeOfVariable.ts, 3, 14))
>a1 : Symbol(a1, Decl(circularInferredTypeOfVariable.ts, 12, 7))

a1 = await bar(a2);
>a1 : Symbol(a1, Decl(circularInferredTypeOfVariable.ts, 12, 7))
>bar : Symbol(bar, Decl(circularInferredTypeOfVariable.ts, 6, 5))
>a2 : Symbol(a2, Decl(circularInferredTypeOfVariable.ts, 15, 11))
}
});
47 changes: 47 additions & 0 deletions tests/baselines/reference/circularInferredTypeOfVariable.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
=== tests/cases/compiler/circularInferredTypeOfVariable.ts ===

// Repro from #14428

(async () => {
>(async () => { function foo(p: string[]): string[] { return []; } function bar(p: string[]): string[] { return []; } let a1: string[] | undefined = []; while (true) { let a2 = foo(a1!); a1 = await bar(a2); }}) : () => Promise<never>
>async () => { function foo(p: string[]): string[] { return []; } function bar(p: string[]): string[] { return []; } let a1: string[] | undefined = []; while (true) { let a2 = foo(a1!); a1 = await bar(a2); }} : () => Promise<never>

function foo(p: string[]): string[] {
>foo : (p: string[]) => string[]
>p : string[]

return [];
>[] : undefined[]
}

function bar(p: string[]): string[] {
>bar : (p: string[]) => string[]
>p : string[]

return [];
>[] : undefined[]
}

let a1: string[] | undefined = [];
>a1 : string[]
>[] : undefined[]

while (true) {
>true : true

let a2 = foo(a1!);
>a2 : string[]
>foo(a1!) : string[]
>foo : (p: string[]) => string[]
>a1! : string[]
>a1 : string[]

a1 = await bar(a2);
>a1 = await bar(a2) : string[]
>a1 : string[]
>await bar(a2) : string[]
>bar(a2) : string[]
>bar : (p: string[]) => string[]
>a2 : string[]
}
});
18 changes: 1 addition & 17 deletions tests/baselines/reference/controlFlowIterationErrors.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(35,17): error
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(46,17): error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'number'.
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(77,13): error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(77,26): error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'.
Type 'true' is not assignable to type 'string | number'.
tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(88,13): error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(88,26): error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'.
Type 'true' is not assignable to type 'string | number'.


==== tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts (8 errors) ====
==== tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts (4 errors) ====

let cond: boolean;

Expand Down Expand Up @@ -104,11 +98,6 @@ tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(88,26): error
x = "0";
while (cond) {
let y = asNumber(x);
~
!!! error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
~
!!! error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'.
!!! error TS2345: Type 'true' is not assignable to type 'string | number'.
x = y + 1;
x;
}
Expand All @@ -120,11 +109,6 @@ tests/cases/conformance/controlFlow/controlFlowIterationErrors.ts(88,26): error
while (cond) {
x;
let y = asNumber(x);
~
!!! error TS7022: 'y' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
~
!!! error TS2345: Argument of type 'string | number | boolean' is not assignable to parameter of type 'string | number'.
!!! error TS2345: Type 'true' is not assignable to type 'string | number'.
x = y + 1;
x;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(18,10): error TS7024: F
tests/cases/compiler/implicitAnyFromCircularInference.ts(23,10): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
tests/cases/compiler/implicitAnyFromCircularInference.ts(26,10): error TS7023: 'h' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
tests/cases/compiler/implicitAnyFromCircularInference.ts(28,14): error TS7023: 'foo' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
tests/cases/compiler/implicitAnyFromCircularInference.ts(41,5): error TS7022: 's' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
tests/cases/compiler/implicitAnyFromCircularInference.ts(46,9): error TS7023: 'x' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.


==== tests/cases/compiler/implicitAnyFromCircularInference.ts (11 errors) ====
==== tests/cases/compiler/implicitAnyFromCircularInference.ts (10 errors) ====

// Error expected
var a: typeof a;
Expand Down Expand Up @@ -71,8 +70,6 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(46,9): error TS7023: 'x
class C {
// Error expected

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess no error expected anymore?

s = foo(this);
~~~~~~~~~~~~~~
!!! error TS7022: 's' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
}

class D {
Expand Down
20 changes: 20 additions & 0 deletions tests/cases/compiler/circularInferredTypeOfVariable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @target: es6

// Repro from #14428

(async () => {
function foo(p: string[]): string[] {
return [];
}

function bar(p: string[]): string[] {
return [];
}

let a1: string[] | undefined = [];

while (true) {
let a2 = foo(a1!);
a1 = await bar(a2);
}
});
2 changes: 1 addition & 1 deletion tests/cases/fourslash/extendArrayInterfaceMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ verify.numberOfErrorsInCurrentFile(1);
// - Supplied parameters do not match any signature of call target.
// - Could not select overload for 'call' expression.

verify.quickInfoAt("y", "var y: any");
verify.quickInfoAt("y", "var y: number");

goTo.eof();
edit.insert("interface Array<T> { pop(def: T): T; }");
Expand Down