Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed an issue with contextual type for intersection properties (take 2) #52095

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f6074e8
Revert "Revert "Fixed an issue with contextual type for intersection …
Andarist Jan 2, 2023
5b9a5dd
Add failing test cases related to RTK
Andarist Jan 2, 2023
bafe4fb
Treat properties of non-generic mapped types as concrete-like
Andarist Jan 4, 2023
6bb681e
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Jan 17, 2023
59414ae
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Mar 22, 2023
19ab1c5
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist May 22, 2023
3ab5773
always concat indexed mapped type substitutions with either types fro…
Andarist May 22, 2023
48faaa3
tweak code
Andarist May 22, 2023
937a0ea
rename variable
Andarist May 22, 2023
5861cc0
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Jun 13, 2023
b9a9285
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Jul 25, 2023
6d24ca8
Add an extra test case related to JSX
Andarist Jul 25, 2023
ea31c96
expand the JSX test
Andarist Jul 25, 2023
f9744e8
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Dec 12, 2023
2fe75f1
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Mar 13, 2024
04bfab1
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Jul 30, 2024
1f9c4ea
add test case for intersected reverse mapped types
Andarist Jul 30, 2024
52effa5
add an extra test case
Andarist Jul 30, 2024
7211b09
specialcase negated-like conditional types
Andarist Jul 31, 2024
0b89c0f
add extra test case
Andarist Aug 1, 2024
7e90ef7
just intersect all of them but filter out anys
Andarist Aug 2, 2024
49bfb73
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Aug 2, 2024
01c20d4
go back to retrieving constituents separately
Andarist Aug 3, 2024
b0012cf
add an extra test case
Andarist Aug 3, 2024
a1c7ba1
add extra test cases
Andarist Aug 3, 2024
e3f9169
return `unknown` when no consistuent property types are found for int…
Andarist Aug 3, 2024
8645b94
small pr feedback
Andarist Aug 5, 2024
dac6f70
tighten up the check in `isExcludedMappedPropertyName`
Andarist Aug 6, 2024
adee09f
address PR feedback
Andarist Aug 6, 2024
e9502f9
fixed typo
Andarist Aug 7, 2024
f96d66e
Merge remote-tracking branch 'origin/main' into fix/contextual-inters…
Andarist Aug 19, 2024
c0e99ae
add extra test cases
Andarist Aug 19, 2024
f218b97
handle filtering mapped types as types that might exclude properties
Andarist Aug 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 91 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31672,33 +31672,106 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
}

function isExcludedMappedPropertyName(constraint: Type, propertyNameType: Type): boolean {
if (constraint.flags & TypeFlags.Conditional) {
const type = constraint as ConditionalType;
return !!(getReducedType(getTrueTypeFromConditionalType(type)).flags & TypeFlags.Never) &&
getActualTypeVariable(getFalseTypeFromConditionalType(type)) === getActualTypeVariable(type.checkType) &&
isTypeAssignableTo(propertyNameType, type.extendsType);
}
if (constraint.flags & TypeFlags.Intersection) {
return some((constraint as IntersectionType).types, t => isExcludedMappedPropertyName(t, propertyNameType));
}
return false;
}

function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
return mapType(type, t => {
if (isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping) {
const constraint = getConstraintTypeFromMappedType(t);
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
return substituteIndexedMappedType(t, propertyNameType);
}
}
else if (t.flags & TypeFlags.StructuredType) {
const prop = getPropertyOfType(t, name);
if (prop) {
return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional));
if (t.flags & TypeFlags.Intersection) {
let types: Type[] | undefined;
let indexInfoCandidates: Type[] | undefined;
let ignoreIndexInfos = false;
for (const constituentType of (t as IntersectionType).types) {
if (!(constituentType.flags & TypeFlags.Object)) {
continue;
}
if (isGenericMappedType(constituentType) && getMappedTypeNameTypeKind(constituentType) !== MappedTypeNameTypeKind.Remapping) {
const substitutedType = getIndexedMappedTypeSubstitutedTypeOfContextualType(constituentType, name, nameType);
types = appendContextualPropertyTypeConstituent(types, substitutedType);
continue;
}
const propertyType = getTypeOfConcretePropertyOfContextualType(constituentType, name);
if (!propertyType) {
if (!ignoreIndexInfos) {
indexInfoCandidates = append(indexInfoCandidates, constituentType);
}
continue;
}
ignoreIndexInfos = true;
indexInfoCandidates = undefined;
types = appendContextualPropertyTypeConstituent(types, propertyType);
}
if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) {
const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
if (restType) {
return restType;
if (indexInfoCandidates) {
for (const candidate of indexInfoCandidates) {
const indexInfoType = getTypeFromIndexInfosOfContextualType(candidate, name, nameType);
types = appendContextualPropertyTypeConstituent(types, indexInfoType);
}
}
return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
if (!types) {
return;
}
if (types.length === 1) {
return types[0];
}
return getIntersectionType(types);
}
return undefined;
if (!(t.flags & TypeFlags.Object)) {
return;
}
return isGenericMappedType(t) && getMappedTypeNameTypeKind(t) !== MappedTypeNameTypeKind.Remapping
? getIndexedMappedTypeSubstitutedTypeOfContextualType(t, name, nameType)
: getTypeOfConcretePropertyOfContextualType(t, name) ?? getTypeFromIndexInfosOfContextualType(t, name, nameType);
}, /*noReductions*/ true);
}

function appendContextualPropertyTypeConstituent(types: Type[] | undefined, type: Type | undefined) {
// any doesn't provide any contextual information but could spoil the overall result by nullifying contextual information provided by other intersection constituents
// 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
return type ? append(types, type.flags & TypeFlags.Any ? unknownType : type) : types;
}

function getIndexedMappedTypeSubstitutedTypeOfContextualType(type: MappedType, name: __String, nameType: Type | undefined) {
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
const constraint = getConstraintTypeFromMappedType(type);
// special case for conditional types pretending to be negated types
if (type.nameType && isExcludedMappedPropertyName(type.nameType, propertyNameType) || isExcludedMappedPropertyName(constraint, propertyNameType)) {
return;
}
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
if (!isTypeAssignableTo(propertyNameType, constraintOfConstraint)) {
return;
}
return substituteIndexedMappedType(type, propertyNameType);
}

function getTypeOfConcretePropertyOfContextualType(type: Type, name: __String) {
const prop = getPropertyOfType(type, name);
if (!prop || isCircularMappedProperty(prop)) {
return;
}
return removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional));
}

function getTypeFromIndexInfosOfContextualType(type: Type, name: __String, nameType: Type | undefined) {
if (isTupleType(type) && isNumericLiteralName(name) && +name >= 0) {
const restType = getElementTypeOfSliceOfTupleType(type, type.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
if (restType) {
return restType;
}
}
return findApplicableIndexInfo(getIndexInfosOfStructuredType(type), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type;
}

// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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; }'.
contextualPropertyOfGenericFilteringMappedType.ts(38,11): error TS7006: Parameter 'value' implicitly has an 'any' type.
contextualPropertyOfGenericFilteringMappedType.ts(38,18): error TS7006: Parameter 'key' implicitly has an 'any' type.


==== contextualPropertyOfGenericFilteringMappedType.ts (3 errors) ====
declare function f1<T extends object>(
data: T,
handlers: { [P in keyof T as P]: (value: T[P], prop: P) => void },
): void;

f1(
{
foo: 0,
bar: "",
},
{
foo: (value, key) => {},
bar: (value, key) => {},
},
);

declare function 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;

f2(
{
foo: 0,
bar: "",
},
{
bar: (value, key) => {},
},
);

f2(
{
foo: 0,
bar: "",
},
{
foo: (value, key) => {
~~~
!!! error TS2353: Object literal may only specify known properties, and 'foo' does not exist in type '{ bar: (value: string, prop: "bar") => void; }'.
~~~~~
!!! error TS7006: Parameter 'value' implicitly has an 'any' type.
~~~
!!! error TS7006: Parameter 'key' implicitly has an 'any' type.
// implicit `any`s
},
},
);

Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,24 @@ f2(
},
);

f2(
>f2 : Symbol(f2, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 14, 2))
{
foo: 0,
>foo : Symbol(foo, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 32, 3))

bar: "",
>bar : Symbol(bar, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 33, 11))

},
{
foo: (value, key) => {
>foo : Symbol(foo, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 36, 3))
>value : Symbol(value, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 37, 10))
>key : Symbol(key, Decl(contextualPropertyOfGenericFilteringMappedType.ts, 37, 16))

// implicit `any`s
},
},
);

Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,44 @@ f2(
},
);

f2(
>f2( { foo: 0, bar: "", }, { foo: (value, key) => { // implicit `any`s }, },) : void
> : ^^^^
>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
> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
{
>{ foo: 0, bar: "", } : { foo: number; bar: string; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

foo: 0,
>foo : number
> : ^^^^^^
>0 : 0
> : ^

bar: "",
>bar : string
> : ^^^^^^
>"" : ""
> : ^^

},
{
>{ foo: (value, key) => { // implicit `any`s }, } : { foo: (value: any, key: any) => void; }
> : ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^

foo: (value, key) => {
>foo : (value: any, key: any) => void
> : ^ ^^^^^^^ ^^^^^^^^^^^^^^
>(value, key) => { // implicit `any`s } : (value: any, key: any) => void
> : ^ ^^^^^^^ ^^^^^^^^^^^^^^
>value : any
> : ^^^
>key : any
> : ^^^

// implicit `any`s
},
},
);

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//// [tests/cases/compiler/contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts] ////

=== contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts ===
type ComponentType<P> = (p: P) => any;
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 19))
>p : Symbol(p, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 25))
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 19))

type ComponentProps<C> = C extends ComponentType<infer P> ? P : never;
>ComponentProps : Symbol(ComponentProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 38))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 20))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 20))
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 54))
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 54))

type Attrs<P, A extends Partial<P>> = A;
>Attrs : Symbol(Attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 70))
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 11))
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 13))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 11))
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 13))

interface StyledFunction<
>StyledFunction : Symbol(StyledFunction, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 40))

C extends ComponentType<any>,
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))

O extends object = {},
>O : Symbol(O, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 6, 31))

A extends keyof any = never,
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 7, 24))

> {
attrs<
>attrs : Symbol(StyledFunction.attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 9, 3))

U,
>U : Symbol(U, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 10, 8))

NewA extends Partial<ComponentProps<C> & U> & {
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>ComponentProps : Symbol(ComponentProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 38))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
>U : Symbol(U, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 10, 8))

[others: string]: any;
>others : Symbol(others, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 13, 7))

} = {},
>(
attrs: Attrs<ComponentProps<C> & U, NewA>,
>attrs : Symbol(attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 15, 4))
>Attrs : Symbol(Attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 1, 70))
>ComponentProps : Symbol(ComponentProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 38))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
>U : Symbol(U, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 10, 8))
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))

): StyledFunction<C, O & NewA, A | keyof NewA>;
>StyledFunction : Symbol(StyledFunction, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 40))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 5, 25))
>O : Symbol(O, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 6, 31))
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
>A : Symbol(A, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 7, 24))
>NewA : Symbol(NewA, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 11, 6))
}

interface StyledInterface {
>StyledInterface : Symbol(StyledInterface, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 18, 1))

<C extends ComponentType<any>>(component: C): StyledFunction<C>;
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 3))
>ComponentType : Symbol(ComponentType, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 0, 0))
>component : Symbol(component, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 33))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 3))
>StyledFunction : Symbol(StyledFunction, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 3, 40))
>C : Symbol(C, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 21, 3))
}

declare const styled: StyledInterface;
>styled : Symbol(styled, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 13))
>StyledInterface : Symbol(StyledInterface, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 18, 1))

interface BaseProps {
>BaseProps : Symbol(BaseProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 38))

as?: "select" | "input";
>as : Symbol(BaseProps.as, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 26, 21))
}

declare const Flex: (props: BaseProps) => null;
>Flex : Symbol(Flex, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 30, 13))
>props : Symbol(props, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 30, 21))
>BaseProps : Symbol(BaseProps, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 38))

export const StyledSelect = styled(Flex).attrs({
>StyledSelect : Symbol(StyledSelect, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 32, 12))
>styled(Flex).attrs : Symbol(StyledFunction.attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 9, 3))
>styled : Symbol(styled, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 24, 13))
>Flex : Symbol(Flex, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 30, 13))
>attrs : Symbol(StyledFunction.attrs, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 9, 3))

as: "select",
>as : Symbol(as, Decl(contextualTypeBasedOnIntersectionWithAnyInTheMix1.ts, 32, 48))

});

Loading