Skip to content

Commit e6edc56

Browse files
authored
Fixed an issue with contextual type for intersection properties (take 2) (#52095)
1 parent 6894ff7 commit e6edc56

File tree

44 files changed

+4767
-18
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4767
-18
lines changed

src/compiler/checker.ts

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31672,33 +31672,106 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3167231672
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
3167331673
}
3167431674

31675+
function isExcludedMappedPropertyName(constraint: Type, propertyNameType: Type): boolean {
31676+
if (constraint.flags & TypeFlags.Conditional) {
31677+
const type = constraint as ConditionalType;
31678+
return !!(getReducedType(getTrueTypeFromConditionalType(type)).flags & TypeFlags.Never) &&
31679+
getActualTypeVariable(getFalseTypeFromConditionalType(type)) === getActualTypeVariable(type.checkType) &&
31680+
isTypeAssignableTo(propertyNameType, type.extendsType);
31681+
}
31682+
if (constraint.flags & TypeFlags.Intersection) {
31683+
return some((constraint as IntersectionType).types, t => isExcludedMappedPropertyName(t, propertyNameType));
31684+
}
31685+
return false;
31686+
}
31687+
3167531688
function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
3167631689
return mapType(type, t => {
31677-
if (isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping) {
31678-
const constraint = getConstraintTypeFromMappedType(t);
31679-
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
31680-
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
31681-
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
31682-
return substituteIndexedMappedType(t, propertyNameType);
31683-
}
31684-
}
31685-
else if (t.flags & TypeFlags.StructuredType) {
31686-
const prop = getPropertyOfType(t, name);
31687-
if (prop) {
31688-
return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional));
31690+
if (t.flags & TypeFlags.Intersection) {
31691+
let types: Type[] | undefined;
31692+
let indexInfoCandidates: Type[] | undefined;
31693+
let ignoreIndexInfos = false;
31694+
for (const constituentType of (t as IntersectionType).types) {
31695+
if (!(constituentType.flags & TypeFlags.Object)) {
31696+
continue;
31697+
}
31698+
if (isGenericMappedType(constituentType) && getMappedTypeNameTypeKind(constituentType) !== MappedTypeNameTypeKind.Remapping) {
31699+
const substitutedType = getIndexedMappedTypeSubstitutedTypeOfContextualType(constituentType, name, nameType);
31700+
types = appendContextualPropertyTypeConstituent(types, substitutedType);
31701+
continue;
31702+
}
31703+
const propertyType = getTypeOfConcretePropertyOfContextualType(constituentType, name);
31704+
if (!propertyType) {
31705+
if (!ignoreIndexInfos) {
31706+
indexInfoCandidates = append(indexInfoCandidates, constituentType);
31707+
}
31708+
continue;
31709+
}
31710+
ignoreIndexInfos = true;
31711+
indexInfoCandidates = undefined;
31712+
types = appendContextualPropertyTypeConstituent(types, propertyType);
3168931713
}
31690-
if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) {
31691-
const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
31692-
if (restType) {
31693-
return restType;
31714+
if (indexInfoCandidates) {
31715+
for (const candidate of indexInfoCandidates) {
31716+
const indexInfoType = getTypeFromIndexInfosOfContextualType(candidate, name, nameType);
31717+
types = appendContextualPropertyTypeConstituent(types, indexInfoType);
3169431718
}
3169531719
}
31696-
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
31720+
if (!types) {
31721+
return;
31722+
}
31723+
if (types.length === 1) {
31724+
return types[0];
31725+
}
31726+
return getIntersectionType(types);
3169731727
}
31698-
return undefined;
31728+
if (!(t.flags & TypeFlags.Object)) {
31729+
return;
31730+
}
31731+
return isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping
31732+
? getIndexedMappedTypeSubstitutedTypeOfContextualType(t, name, nameType)
31733+
: getTypeOfConcretePropertyOfContextualType(t, name) ?? getTypeFromIndexInfosOfContextualType(t, name, nameType);
3169931734
}, /*noReductions*/ true);
3170031735
}
3170131736

31737+
function appendContextualPropertyTypeConstituent(types: Type[] | undefined, type: Type | undefined) {
31738+
// any doesn't provide any contextual information but could spoil the overall result by nullifying contextual information provided by other intersection constituents
31739+
// so it gets replaced with `unknown` as `T & unknown` is just `T` and all types computed based on the contextual information provided by other constituens are still assignable to any
31740+
return type ? append(types, type.flags & TypeFlags.Any ? unknownType : type) : types;
31741+
}
31742+
31743+
function getIndexedMappedTypeSubstitutedTypeOfContextualType(type: MappedType, name: __String, nameType: Type | undefined) {
31744+
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
31745+
const constraint = getConstraintTypeFromMappedType(type);
31746+
// special case for conditional types pretending to be negated types
31747+
if (type.nameType && isExcludedMappedPropertyName(type.nameType, propertyNameType) || isExcludedMappedPropertyName(constraint, propertyNameType)) {
31748+
return;
31749+
}
31750+
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
31751+
if (!isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
31752+
return;
31753+
}
31754+
return substituteIndexedMappedType(type, propertyNameType);
31755+
}
31756+
31757+
function getTypeOfConcretePropertyOfContextualType(type: Type, name: __String) {
31758+
const prop = getPropertyOfType(type, name);
31759+
if (!prop || isCircularMappedProperty(prop)) {
31760+
return;
31761+
}
31762+
return removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional));
31763+
}
31764+
31765+
function getTypeFromIndexInfosOfContextualType(type: Type, name: __String, nameType: Type | undefined) {
31766+
if (isTupleType(type) && isNumericLiteralName(name) && +name >= 0) {
31767+
const restType = getElementTypeOfSliceOfTupleType(type, type.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
31768+
if (restType) {
31769+
return restType;
31770+
}
31771+
}
31772+
return findApplicableIndexInfo(getIndexInfosOfStructuredType(type), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
31773+
}
31774+
3170231775
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
3170331776
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
3170431777
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
contextualPropertyOfGenericFilteringMappedType.ts(38,5): error TS2353: Object literal may only specify known properties, and 'foo' does not exist in type '{ bar: (value: string, prop: "bar") => void; }'.
2+
contextualPropertyOfGenericFilteringMappedType.ts(38,11): error TS7006: Parameter 'value' implicitly has an 'any' type.
3+
contextualPropertyOfGenericFilteringMappedType.ts(38,18): error TS7006: Parameter 'key' implicitly has an 'any' type.
4+
5+
6+
==== contextualPropertyOfGenericFilteringMappedType.ts (3 errors) ====
7+
declare function f1<T extends object>(
8+
data: T,
9+
handlers: { [P in keyof T as P]: (value: T[P], prop: P) => void },
10+
): void;
11+
12+
f1(
13+
{
14+
foo: 0,
15+
bar: "",
16+
},
17+
{
18+
foo: (value, key) => {},
19+
bar: (value, key) => {},
20+
},
21+
);
22+
23+
declare function f2<T extends object>(
24+
data: T,
25+
handlers: { [P in keyof T as T[P] extends string ? P : never]: (value: T[P], prop: P) => void },
26+
): void;
27+
28+
f2(
29+
{
30+
foo: 0,
31+
bar: "",
32+
},
33+
{
34+
bar: (value, key) => {},
35+
},
36+
);
37+
38+
f2(
39+
{
40+
foo: 0,
41+
bar: "",
42+
},
43+
{
44+
foo: (value, key) => {
45+
~~~
46+
!!! error TS2353: Object literal may only specify known properties, and 'foo' does not exist in type '{ bar: (value: string, prop: "bar") => void; }'.
47+
~~~~~
48+
!!! error TS7006: Parameter 'value' implicitly has an 'any' type.
49+
~~~
50+
!!! error TS7006: Parameter 'key' implicitly has an 'any' type.
51+
// implicit `any`s
52+
},
53+
},
54+
);
55+

tests/baselines/reference/contextualPropertyOfGenericFilteringMappedType.symbols

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,24 @@ f2(
8888
},
8989
);
9090

91+
f2(
92+
>f2 : Symbol(f2, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 14, 2))
93+
{
94+
foo: 0,
95+
>foo : Symbol(foo, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 32, 3))
96+
97+
bar: "",
98+
>bar : Symbol(bar, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 33, 11))
99+
100+
},
101+
{
102+
foo: (value, key) => {
103+
>foo : Symbol(foo, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 36, 3))
104+
>value : Symbol(value, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 37, 10))
105+
>key : Symbol(key, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 37, 16))
106+
107+
// implicit `any`s
108+
},
109+
},
110+
);
111+

tests/baselines/reference/contextualPropertyOfGenericFilteringMappedType.types

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,44 @@ f2(
125125
},
126126
);
127127

128+
f2(
129+
>f2( { foo: 0, bar: "", }, { foo: (value, key) => { // implicit `any`s }, },) : void
130+
> : ^^^^
131+
>f2 : <T extends object>(data: T, handlers: { [P in keyof T as T[P] extends string ? P : never]: (value: T[P], prop: P) => void; }) => void
132+
> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
133+
{
134+
>{ foo: 0, bar: "", } : { foo: number; bar: string; }
135+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
136+
137+
foo: 0,
138+
>foo : number
139+
> : ^^^^^^
140+
>0 : 0
141+
> : ^
142+
143+
bar: "",
144+
>bar : string
145+
> : ^^^^^^
146+
>"" : ""
147+
> : ^^
148+
149+
},
150+
{
151+
>{ foo: (value, key) => { // implicit `any`s }, } : { foo: (value: any, key: any) => void; }
152+
> : ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^
153+
154+
foo: (value, key) => {
155+
>foo : (value: any, key: any) => void
156+
> : ^ ^^^^^^^ ^^^^^^^^^^^^^^
157+
>(value, key) => { // implicit `any`s } : (value: any, key: any) => void
158+
> : ^ ^^^^^^^ ^^^^^^^^^^^^^^
159+
>value : any
160+
> : ^^^
161+
>key : any
162+
> : ^^^
163+
164+
// implicit `any`s
165+
},
166+
},
167+
);
168+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//// [tests/cases/compiler/contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts] ////
2+
3+
=== contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts ===
4+
type ComponentType<P> = (p: P) => any;
5+
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
6+
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 19))
7+
>p : Symbol(p, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 25))
8+
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 19))
9+
10+
type ComponentProps<C> = C extends ComponentType<infer P> ? P : never;
11+
>ComponentProps : Symbol(ComponentProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 38))
12+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 20))
13+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 20))
14+
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
15+
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 54))
16+
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 54))
17+
18+
type Attrs<P, A extends Partial<P>> = A;
19+
>Attrs : Symbol(Attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 70))
20+
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 11))
21+
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 13))
22+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
23+
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 11))
24+
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 13))
25+
26+
interface StyledFunction<
27+
>StyledFunction : Symbol(StyledFunction, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 40))
28+
29+
C extends ComponentType<any>,
30+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
31+
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
32+
33+
O extends object = {},
34+
>O : Symbol(O, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 6, 31))
35+
36+
A extends keyof any = never,
37+
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 7, 24))
38+
39+
> {
40+
attrs<
41+
>attrs : Symbol(StyledFunction.attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 9, 3))
42+
43+
U,
44+
>U : Symbol(U, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 10, 8))
45+
46+
NewA extends Partial<ComponentProps<C> & U> & {
47+
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
48+
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
49+
>ComponentProps : Symbol(ComponentProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 38))
50+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
51+
>U : Symbol(U, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 10, 8))
52+
53+
[others: string]: any;
54+
>others : Symbol(others, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 13, 7))
55+
56+
} = {},
57+
>(
58+
attrs: Attrs<ComponentProps<C> & U, NewA>,
59+
>attrs : Symbol(attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 15, 4))
60+
>Attrs : Symbol(Attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 70))
61+
>ComponentProps : Symbol(ComponentProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 38))
62+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
63+
>U : Symbol(U, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 10, 8))
64+
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
65+
66+
): StyledFunction<C, O & NewA, A | keyof NewA>;
67+
>StyledFunction : Symbol(StyledFunction, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 40))
68+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
69+
>O : Symbol(O, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 6, 31))
70+
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
71+
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 7, 24))
72+
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
73+
}
74+
75+
interface StyledInterface {
76+
>StyledInterface : Symbol(StyledInterface, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 18, 1))
77+
78+
<C extends ComponentType<any>>(component: C): StyledFunction<C>;
79+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 3))
80+
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
81+
>component : Symbol(component, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 33))
82+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 3))
83+
>StyledFunction : Symbol(StyledFunction, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 40))
84+
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 3))
85+
}
86+
87+
declare const styled: StyledInterface;
88+
>styled : Symbol(styled, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 13))
89+
>StyledInterface : Symbol(StyledInterface, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 18, 1))
90+
91+
interface BaseProps {
92+
>BaseProps : Symbol(BaseProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 38))
93+
94+
as?: "select" | "input";
95+
>as : Symbol(BaseProps.as, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 26, 21))
96+
}
97+
98+
declare const Flex: (props: BaseProps) => null;
99+
>Flex : Symbol(Flex, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 30, 13))
100+
>props : Symbol(props, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 30, 21))
101+
>BaseProps : Symbol(BaseProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 38))
102+
103+
export const StyledSelect = styled(Flex).attrs({
104+
>StyledSelect : Symbol(StyledSelect, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 32, 12))
105+
>styled(Flex).attrs : Symbol(StyledFunction.attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 9, 3))
106+
>styled : Symbol(styled, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 13))
107+
>Flex : Symbol(Flex, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 30, 13))
108+
>attrs : Symbol(StyledFunction.attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 9, 3))
109+
110+
as: "select",
111+
>as : Symbol(as, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 32, 48))
112+
113+
});
114+

0 commit comments

Comments
 (0)