Excess property understands conditional types (#25584)

Previously it did not, causing misleading excess property errors. Note
that assignability errors with conditional types are still usually
confusing. This PR doesn't address that.

Also, make sure that exact matches in getSpellingSuggestion are skipped.
This commit is contained in:
Nathan Shively-Sanders 2018-07-11 11:24:40 -07:00 committed by GitHub
parent 52486ae362
commit 42a2d9e568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 149 additions and 2 deletions

View file

@ -17406,7 +17406,7 @@ namespace ts {
*/
function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean {
if (targetType.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
const resolved = resolveStructuredTypeMembers(targetType as ObjectType);
if (resolved.stringIndexInfo ||
resolved.numberIndexInfo && isNumericLiteralName(name) ||
getPropertyOfObjectType(targetType, name) ||
@ -17416,12 +17416,16 @@ namespace ts {
}
}
else if (targetType.flags & TypeFlags.UnionOrIntersection) {
for (const t of (<UnionOrIntersectionType>targetType).types) {
for (const t of (targetType as UnionOrIntersectionType).types) {
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
return true;
}
}
}
else if (targetType.flags & TypeFlags.Conditional) {
return isKnownProperty((targetType as ConditionalType).root.trueType, name, isComparingJsxAttributes) ||
isKnownProperty((targetType as ConditionalType).root.falseType, name, isComparingJsxAttributes);
}
return false;
}

View file

@ -1866,6 +1866,9 @@ namespace ts {
if (candidateName !== undefined && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference) {
const candidateNameLowerCase = candidateName.toLowerCase();
if (candidateNameLowerCase === nameLowerCase) {
if (candidateName === name) {
continue;
}
return candidate;
}
if (justCheckExactMatches) {

View file

@ -0,0 +1,24 @@
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(8,5): error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something<A>'.
Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'.
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,33): error TS2322: Type 'A' is not assignable to type 'Something<A>["arr"]'.
Type 'object' is not assignable to type 'Something<A>["arr"]'.
==== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts (2 errors) ====
type Something<T> = { test: string } & (T extends object ? {
arg: T
} : {
arg?: undefined
});
function testFunc2<A extends object>(a: A, sa: Something<A>) {
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
~~
!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something<A>'.
!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'.
sa = { test: 'bye', arg: a, arr: a } // excess
~~~
!!! error TS2322: Type 'A' is not assignable to type 'Something<A>["arr"]'.
!!! error TS2322: Type 'object' is not assignable to type 'Something<A>["arr"]'.
}

View file

@ -0,0 +1,18 @@
//// [conditionalTypesExcessProperties.ts]
type Something<T> = { test: string } & (T extends object ? {
arg: T
} : {
arg?: undefined
});
function testFunc2<A extends object>(a: A, sa: Something<A>) {
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
sa = { test: 'bye', arg: a, arr: a } // excess
}
//// [conditionalTypesExcessProperties.js]
function testFunc2(a, sa) {
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
sa = { test: 'bye', arg: a, arr: a }; // excess
}

View file

@ -0,0 +1,41 @@
=== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts ===
type Something<T> = { test: string } & (T extends object ? {
>Something : Symbol(Something, Decl(conditionalTypesExcessProperties.ts, 0, 0))
>T : Symbol(T, Decl(conditionalTypesExcessProperties.ts, 0, 15))
>test : Symbol(test, Decl(conditionalTypesExcessProperties.ts, 0, 21))
>T : Symbol(T, Decl(conditionalTypesExcessProperties.ts, 0, 15))
arg: T
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 0, 61))
>T : Symbol(T, Decl(conditionalTypesExcessProperties.ts, 0, 15))
} : {
arg?: undefined
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 2, 5))
});
function testFunc2<A extends object>(a: A, sa: Something<A>) {
>testFunc2 : Symbol(testFunc2, Decl(conditionalTypesExcessProperties.ts, 4, 7))
>A : Symbol(A, Decl(conditionalTypesExcessProperties.ts, 6, 19))
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
>A : Symbol(A, Decl(conditionalTypesExcessProperties.ts, 6, 19))
>sa : Symbol(sa, Decl(conditionalTypesExcessProperties.ts, 6, 42))
>Something : Symbol(Something, Decl(conditionalTypesExcessProperties.ts, 0, 0))
>A : Symbol(A, Decl(conditionalTypesExcessProperties.ts, 6, 19))
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
>sa : Symbol(sa, Decl(conditionalTypesExcessProperties.ts, 6, 42))
>test : Symbol(test, Decl(conditionalTypesExcessProperties.ts, 7, 10))
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 7, 22))
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
sa = { test: 'bye', arg: a, arr: a } // excess
>sa : Symbol(sa, Decl(conditionalTypesExcessProperties.ts, 6, 42))
>test : Symbol(test, Decl(conditionalTypesExcessProperties.ts, 8, 10))
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 8, 23))
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
>arr : Symbol(arr, Decl(conditionalTypesExcessProperties.ts, 8, 31))
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
}

View file

@ -0,0 +1,47 @@
=== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts ===
type Something<T> = { test: string } & (T extends object ? {
>Something : Something<T>
>T : T
>test : string
>T : T
arg: T
>arg : T
>T : T
} : {
arg?: undefined
>arg : undefined
});
function testFunc2<A extends object>(a: A, sa: Something<A>) {
>testFunc2 : <A extends object>(a: A, sa: Something<A>) => void
>A : A
>a : A
>A : A
>sa : Something<A>
>Something : Something<T>
>A : A
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
>sa = { test: 'hi', arg: a } : { test: string; arg: A; }
>sa : Something<A>
>{ test: 'hi', arg: a } : { test: string; arg: A; }
>test : string
>'hi' : "hi"
>arg : A
>a : A
sa = { test: 'bye', arg: a, arr: a } // excess
>sa = { test: 'bye', arg: a, arr: a } : { test: string; arg: A; arr: A; }
>sa : Something<A>
>{ test: 'bye', arg: a, arr: a } : { test: string; arg: A; arr: A; }
>test : string
>'bye' : "bye"
>arg : A
>a : A
>arr : A
>a : A
}

View file

@ -0,0 +1,10 @@
type Something<T> = { test: string } & (T extends object ? {
arg: T
} : {
arg?: undefined
});
function testFunc2<A extends object>(a: A, sa: Something<A>) {
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
sa = { test: 'bye', arg: a, arr: a } // excess
}