Skip to content

Commit 42a2d9e

Browse files
authored
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.
1 parent 52486ae commit 42a2d9e

7 files changed

+149
-2
lines changed

src/compiler/checker.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -17406,7 +17406,7 @@ namespace ts {
1740617406
*/
1740717407
function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean {
1740817408
if (targetType.flags & TypeFlags.Object) {
17409-
const resolved = resolveStructuredTypeMembers(<ObjectType>targetType);
17409+
const resolved = resolveStructuredTypeMembers(targetType as ObjectType);
1741017410
if (resolved.stringIndexInfo ||
1741117411
resolved.numberIndexInfo && isNumericLiteralName(name) ||
1741217412
getPropertyOfObjectType(targetType, name) ||
@@ -17416,12 +17416,16 @@ namespace ts {
1741617416
}
1741717417
}
1741817418
else if (targetType.flags & TypeFlags.UnionOrIntersection) {
17419-
for (const t of (<UnionOrIntersectionType>targetType).types) {
17419+
for (const t of (targetType as UnionOrIntersectionType).types) {
1742017420
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
1742117421
return true;
1742217422
}
1742317423
}
1742417424
}
17425+
else if (targetType.flags & TypeFlags.Conditional) {
17426+
return isKnownProperty((targetType as ConditionalType).root.trueType, name, isComparingJsxAttributes) ||
17427+
isKnownProperty((targetType as ConditionalType).root.falseType, name, isComparingJsxAttributes);
17428+
}
1742517429
return false;
1742617430
}
1742717431

src/compiler/core.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,9 @@ namespace ts {
18661866
if (candidateName !== undefined && Math.abs(candidateName.length - nameLowerCase.length) <= maximumLengthDifference) {
18671867
const candidateNameLowerCase = candidateName.toLowerCase();
18681868
if (candidateNameLowerCase === nameLowerCase) {
1869+
if (candidateName === name) {
1870+
continue;
1871+
}
18691872
return candidate;
18701873
}
18711874
if (justCheckExactMatches) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(8,5): error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something<A>'.
2+
Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'.
3+
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,33): error TS2322: Type 'A' is not assignable to type 'Something<A>["arr"]'.
4+
Type 'object' is not assignable to type 'Something<A>["arr"]'.
5+
6+
7+
==== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts (2 errors) ====
8+
type Something<T> = { test: string } & (T extends object ? {
9+
arg: T
10+
} : {
11+
arg?: undefined
12+
});
13+
14+
function testFunc2<A extends object>(a: A, sa: Something<A>) {
15+
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
16+
~~
17+
!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something<A>'.
18+
!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'.
19+
sa = { test: 'bye', arg: a, arr: a } // excess
20+
~~~
21+
!!! error TS2322: Type 'A' is not assignable to type 'Something<A>["arr"]'.
22+
!!! error TS2322: Type 'object' is not assignable to type 'Something<A>["arr"]'.
23+
}
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [conditionalTypesExcessProperties.ts]
2+
type Something<T> = { test: string } & (T extends object ? {
3+
arg: T
4+
} : {
5+
arg?: undefined
6+
});
7+
8+
function testFunc2<A extends object>(a: A, sa: Something<A>) {
9+
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
10+
sa = { test: 'bye', arg: a, arr: a } // excess
11+
}
12+
13+
14+
//// [conditionalTypesExcessProperties.js]
15+
function testFunc2(a, sa) {
16+
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
17+
sa = { test: 'bye', arg: a, arr: a }; // excess
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts ===
2+
type Something<T> = { test: string } & (T extends object ? {
3+
>Something : Symbol(Something, Decl(conditionalTypesExcessProperties.ts, 0, 0))
4+
>T : Symbol(T, Decl(conditionalTypesExcessProperties.ts, 0, 15))
5+
>test : Symbol(test, Decl(conditionalTypesExcessProperties.ts, 0, 21))
6+
>T : Symbol(T, Decl(conditionalTypesExcessProperties.ts, 0, 15))
7+
8+
arg: T
9+
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 0, 61))
10+
>T : Symbol(T, Decl(conditionalTypesExcessProperties.ts, 0, 15))
11+
12+
} : {
13+
arg?: undefined
14+
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 2, 5))
15+
16+
});
17+
18+
function testFunc2<A extends object>(a: A, sa: Something<A>) {
19+
>testFunc2 : Symbol(testFunc2, Decl(conditionalTypesExcessProperties.ts, 4, 7))
20+
>A : Symbol(A, Decl(conditionalTypesExcessProperties.ts, 6, 19))
21+
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
22+
>A : Symbol(A, Decl(conditionalTypesExcessProperties.ts, 6, 19))
23+
>sa : Symbol(sa, Decl(conditionalTypesExcessProperties.ts, 6, 42))
24+
>Something : Symbol(Something, Decl(conditionalTypesExcessProperties.ts, 0, 0))
25+
>A : Symbol(A, Decl(conditionalTypesExcessProperties.ts, 6, 19))
26+
27+
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
28+
>sa : Symbol(sa, Decl(conditionalTypesExcessProperties.ts, 6, 42))
29+
>test : Symbol(test, Decl(conditionalTypesExcessProperties.ts, 7, 10))
30+
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 7, 22))
31+
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
32+
33+
sa = { test: 'bye', arg: a, arr: a } // excess
34+
>sa : Symbol(sa, Decl(conditionalTypesExcessProperties.ts, 6, 42))
35+
>test : Symbol(test, Decl(conditionalTypesExcessProperties.ts, 8, 10))
36+
>arg : Symbol(arg, Decl(conditionalTypesExcessProperties.ts, 8, 23))
37+
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
38+
>arr : Symbol(arr, Decl(conditionalTypesExcessProperties.ts, 8, 31))
39+
>a : Symbol(a, Decl(conditionalTypesExcessProperties.ts, 6, 37))
40+
}
41+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
=== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts ===
2+
type Something<T> = { test: string } & (T extends object ? {
3+
>Something : Something<T>
4+
>T : T
5+
>test : string
6+
>T : T
7+
8+
arg: T
9+
>arg : T
10+
>T : T
11+
12+
} : {
13+
arg?: undefined
14+
>arg : undefined
15+
16+
});
17+
18+
function testFunc2<A extends object>(a: A, sa: Something<A>) {
19+
>testFunc2 : <A extends object>(a: A, sa: Something<A>) => void
20+
>A : A
21+
>a : A
22+
>A : A
23+
>sa : Something<A>
24+
>Something : Something<T>
25+
>A : A
26+
27+
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
28+
>sa = { test: 'hi', arg: a } : { test: string; arg: A; }
29+
>sa : Something<A>
30+
>{ test: 'hi', arg: a } : { test: string; arg: A; }
31+
>test : string
32+
>'hi' : "hi"
33+
>arg : A
34+
>a : A
35+
36+
sa = { test: 'bye', arg: a, arr: a } // excess
37+
>sa = { test: 'bye', arg: a, arr: a } : { test: string; arg: A; arr: A; }
38+
>sa : Something<A>
39+
>{ test: 'bye', arg: a, arr: a } : { test: string; arg: A; arr: A; }
40+
>test : string
41+
>'bye' : "bye"
42+
>arg : A
43+
>a : A
44+
>arr : A
45+
>a : A
46+
}
47+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type Something<T> = { test: string } & (T extends object ? {
2+
arg: T
3+
} : {
4+
arg?: undefined
5+
});
6+
7+
function testFunc2<A extends object>(a: A, sa: Something<A>) {
8+
sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable)
9+
sa = { test: 'bye', arg: a, arr: a } // excess
10+
}

0 commit comments

Comments
 (0)