Merge pull request #17430 from Microsoft/inferenceExcessProperties

Fix inference with excess properties
This commit is contained in:
Anders Hejlsberg
2017-07-27 17:35:37 -07:00
committed by GitHub
5 changed files with 247 additions and 12 deletions

View File

@@ -15247,17 +15247,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;
}
}
@@ -15629,6 +15629,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.
@@ -15638,6 +15639,7 @@ namespace ts {
excludeArgument = new Array(args.length);
}
excludeArgument[i] = true;
excludeCount++;
}
}
}
@@ -15812,12 +15814,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;
}
}
}

View File

@@ -0,0 +1,54 @@
//// [typeInferenceWithExcessProperties.ts]
// Repro from #17041
interface Named {
name: string;
}
function parrot<T extends Named>(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 () {
}
});

View File

@@ -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<T extends Named>(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))
},
});

View File

@@ -0,0 +1,79 @@
=== tests/cases/compiler/typeInferenceWithExcessProperties.ts ===
// Repro from #17041
interface Named {
>Named : Named
name: string;
>name : string
}
function parrot<T extends Named>(obj: T): T {
>parrot : <T extends Named>(obj: T) => T
>T : T
>Named : Named
>obj : T
>T : T
>T : T
return obj;
>obj : T
}
parrot({
>parrot({ name: "TypeScript",}) : { name: string; }
>parrot : <T extends Named>(obj: T) => T
>{ name: "TypeScript",} : { name: string; }
name: "TypeScript",
>name : string
>"TypeScript" : "TypeScript"
});
parrot({
>parrot({ name: "TypeScript", age: 5,}) : { name: string; age: number; }
>parrot : <T extends Named>(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 : <T extends Named>(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 : <T extends Named>(obj: T) => T
>{ name: "TypeScript", sayHello() { },} : { name: string; sayHello(): void; }
name: "TypeScript",
>name : string
>"TypeScript" : "TypeScript"
sayHello() {
>sayHello : () => void
},
});

View File

@@ -0,0 +1,30 @@
// Repro from #17041
interface Named {
name: string;
}
function parrot<T extends Named>(obj: T): T {
return obj;
}
parrot({
name: "TypeScript",
});
parrot({
name: "TypeScript",
age: 5,
});
parrot({
name: "TypeScript",
age: function () { },
});
parrot({
name: "TypeScript",
sayHello() {
},
});