From bd1f8c50a4b7c05736345e42c4592e02c3ae78c9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 26 Jul 2017 07:16:06 -0700 Subject: [PATCH 1/2] Only check excess properties on final types from inference --- src/compiler/checker.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ff45ee0b73..870c5255fcb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15238,17 +15238,17 @@ namespace ts { if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) { // Check spread elements against rest type (from arity check we know spread argument corresponds to a rest parameter) const paramType = getTypeAtPosition(signature, i); - let argType = getEffectiveArgumentType(node, i); - - // If the effective argument type is 'undefined', there is no synthetic type - // for the argument. In that case, we should check the argument. - if (argType === undefined) { - argType = checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); - } - + // If the effective argument type is undefined, there is no synthetic type for the argument. + // In that case, we should check the argument. + const argType = getEffectiveArgumentType(node, i) || + checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); + // If one or more arguments are still excluded (as indicated by a non-null excludeArgument parameter), + // we obtain the regular type of any object literal arguments because we may not have inferred complete + // parameter types yet and therefore excess property checks may yield false positives (see #17041). + const checkArgType = excludeArgument ? getRegularTypeOfObjectLiteral(argType) : argType; // Use argument expression as error location when reporting errors const errorNode = reportErrors ? getEffectiveArgumentErrorNode(node, i, arg) : undefined; - if (!checkTypeRelatedTo(argType, paramType, relation, errorNode, headMessage)) { + if (!checkTypeRelatedTo(checkArgType, paramType, relation, errorNode, headMessage)) { return false; } } @@ -15620,6 +15620,7 @@ namespace ts { // For a decorator, no arguments are susceptible to contextual typing due to the fact // decorators are applied to a declaration by the emitter, and not to an expression. let excludeArgument: boolean[]; + let excludeCount = 0; if (!isDecorator) { // We do not need to call `getEffectiveArgumentCount` here as it only // applies when calculating the number of arguments for a decorator. @@ -15629,6 +15630,7 @@ namespace ts { excludeArgument = new Array(args.length); } excludeArgument[i] = true; + excludeCount++; } } } @@ -15803,12 +15805,17 @@ namespace ts { candidateForArgumentError = candidate; break; } - const index = excludeArgument ? indexOf(excludeArgument, /*value*/ true) : -1; - if (index < 0) { + if (excludeCount === 0) { candidates[candidateIndex] = candidate; return candidate; } - excludeArgument[index] = false; + excludeCount--; + if (excludeCount > 0) { + excludeArgument[indexOf(excludeArgument, /*value*/ true)] = false; + } + else { + excludeArgument = undefined; + } } } From a14144be9c9c40603a2bcac637f7a61573be5c39 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 26 Jul 2017 07:16:26 -0700 Subject: [PATCH 2/2] Add regression test --- .../typeInferenceWithExcessProperties.js | 54 +++++++++++++ .../typeInferenceWithExcessProperties.symbols | 65 +++++++++++++++ .../typeInferenceWithExcessProperties.types | 79 +++++++++++++++++++ .../typeInferenceWithExcessProperties.ts | 30 +++++++ 4 files changed, 228 insertions(+) create mode 100644 tests/baselines/reference/typeInferenceWithExcessProperties.js create mode 100644 tests/baselines/reference/typeInferenceWithExcessProperties.symbols create mode 100644 tests/baselines/reference/typeInferenceWithExcessProperties.types create mode 100644 tests/cases/compiler/typeInferenceWithExcessProperties.ts diff --git a/tests/baselines/reference/typeInferenceWithExcessProperties.js b/tests/baselines/reference/typeInferenceWithExcessProperties.js new file mode 100644 index 00000000000..0630f1c8977 --- /dev/null +++ b/tests/baselines/reference/typeInferenceWithExcessProperties.js @@ -0,0 +1,54 @@ +//// [typeInferenceWithExcessProperties.ts] +// Repro from #17041 + +interface Named { + name: string; +} + +function parrot(obj: T): T { + return obj; +} + + +parrot({ + name: "TypeScript", +}); + +parrot({ + name: "TypeScript", + age: 5, +}); + +parrot({ + name: "TypeScript", + age: function () { }, +}); + +parrot({ + name: "TypeScript", + sayHello() { + }, +}); + + +//// [typeInferenceWithExcessProperties.js] +// Repro from #17041 +function parrot(obj) { + return obj; +} +parrot({ + name: "TypeScript" +}); +parrot({ + name: "TypeScript", + age: 5 +}); +parrot({ + name: "TypeScript", + age: function () { } +}); +parrot({ + name: "TypeScript", + sayHello: function () { + } +}); diff --git a/tests/baselines/reference/typeInferenceWithExcessProperties.symbols b/tests/baselines/reference/typeInferenceWithExcessProperties.symbols new file mode 100644 index 00000000000..1c4d79cda48 --- /dev/null +++ b/tests/baselines/reference/typeInferenceWithExcessProperties.symbols @@ -0,0 +1,65 @@ +=== tests/cases/compiler/typeInferenceWithExcessProperties.ts === +// Repro from #17041 + +interface Named { +>Named : Symbol(Named, Decl(typeInferenceWithExcessProperties.ts, 0, 0)) + + name: string; +>name : Symbol(Named.name, Decl(typeInferenceWithExcessProperties.ts, 2, 17)) +} + +function parrot(obj: T): T { +>parrot : Symbol(parrot, Decl(typeInferenceWithExcessProperties.ts, 4, 1)) +>T : Symbol(T, Decl(typeInferenceWithExcessProperties.ts, 6, 16)) +>Named : Symbol(Named, Decl(typeInferenceWithExcessProperties.ts, 0, 0)) +>obj : Symbol(obj, Decl(typeInferenceWithExcessProperties.ts, 6, 33)) +>T : Symbol(T, Decl(typeInferenceWithExcessProperties.ts, 6, 16)) +>T : Symbol(T, Decl(typeInferenceWithExcessProperties.ts, 6, 16)) + + return obj; +>obj : Symbol(obj, Decl(typeInferenceWithExcessProperties.ts, 6, 33)) +} + + +parrot({ +>parrot : Symbol(parrot, Decl(typeInferenceWithExcessProperties.ts, 4, 1)) + + name: "TypeScript", +>name : Symbol(name, Decl(typeInferenceWithExcessProperties.ts, 11, 8)) + +}); + +parrot({ +>parrot : Symbol(parrot, Decl(typeInferenceWithExcessProperties.ts, 4, 1)) + + name: "TypeScript", +>name : Symbol(name, Decl(typeInferenceWithExcessProperties.ts, 15, 8)) + + age: 5, +>age : Symbol(age, Decl(typeInferenceWithExcessProperties.ts, 16, 23)) + +}); + +parrot({ +>parrot : Symbol(parrot, Decl(typeInferenceWithExcessProperties.ts, 4, 1)) + + name: "TypeScript", +>name : Symbol(name, Decl(typeInferenceWithExcessProperties.ts, 20, 8)) + + age: function () { }, +>age : Symbol(age, Decl(typeInferenceWithExcessProperties.ts, 21, 23)) + +}); + +parrot({ +>parrot : Symbol(parrot, Decl(typeInferenceWithExcessProperties.ts, 4, 1)) + + name: "TypeScript", +>name : Symbol(name, Decl(typeInferenceWithExcessProperties.ts, 25, 8)) + + sayHello() { +>sayHello : Symbol(sayHello, Decl(typeInferenceWithExcessProperties.ts, 26, 23)) + + }, +}); + diff --git a/tests/baselines/reference/typeInferenceWithExcessProperties.types b/tests/baselines/reference/typeInferenceWithExcessProperties.types new file mode 100644 index 00000000000..af3e1baae90 --- /dev/null +++ b/tests/baselines/reference/typeInferenceWithExcessProperties.types @@ -0,0 +1,79 @@ +=== tests/cases/compiler/typeInferenceWithExcessProperties.ts === +// Repro from #17041 + +interface Named { +>Named : Named + + name: string; +>name : string +} + +function parrot(obj: T): T { +>parrot : (obj: T) => T +>T : T +>Named : Named +>obj : T +>T : T +>T : T + + return obj; +>obj : T +} + + +parrot({ +>parrot({ name: "TypeScript",}) : { name: string; } +>parrot : (obj: T) => T +>{ name: "TypeScript",} : { name: string; } + + name: "TypeScript", +>name : string +>"TypeScript" : "TypeScript" + +}); + +parrot({ +>parrot({ name: "TypeScript", age: 5,}) : { name: string; age: number; } +>parrot : (obj: T) => T +>{ name: "TypeScript", age: 5,} : { name: string; age: number; } + + name: "TypeScript", +>name : string +>"TypeScript" : "TypeScript" + + age: 5, +>age : number +>5 : 5 + +}); + +parrot({ +>parrot({ name: "TypeScript", age: function () { },}) : { name: string; age: () => void; } +>parrot : (obj: T) => T +>{ name: "TypeScript", age: function () { },} : { name: string; age: () => void; } + + name: "TypeScript", +>name : string +>"TypeScript" : "TypeScript" + + age: function () { }, +>age : () => void +>function () { } : () => void + +}); + +parrot({ +>parrot({ name: "TypeScript", sayHello() { },}) : { name: string; sayHello(): void; } +>parrot : (obj: T) => T +>{ name: "TypeScript", sayHello() { },} : { name: string; sayHello(): void; } + + name: "TypeScript", +>name : string +>"TypeScript" : "TypeScript" + + sayHello() { +>sayHello : () => void + + }, +}); + diff --git a/tests/cases/compiler/typeInferenceWithExcessProperties.ts b/tests/cases/compiler/typeInferenceWithExcessProperties.ts new file mode 100644 index 00000000000..3cf9f23a1ed --- /dev/null +++ b/tests/cases/compiler/typeInferenceWithExcessProperties.ts @@ -0,0 +1,30 @@ +// Repro from #17041 + +interface Named { + name: string; +} + +function parrot(obj: T): T { + return obj; +} + + +parrot({ + name: "TypeScript", +}); + +parrot({ + name: "TypeScript", + age: 5, +}); + +parrot({ + name: "TypeScript", + age: function () { }, +}); + +parrot({ + name: "TypeScript", + sayHello() { + }, +});