Skip to content

Commit

Permalink
Fixed an issue with contextual type for intersection properties (take…
Browse files Browse the repository at this point in the history
… 2) (#52095)
  • Loading branch information
Andarist authored Aug 19, 2024
1 parent 6894ff7 commit e6edc56
Show file tree
Hide file tree
Showing 44 changed files with 4,767 additions and 18 deletions.
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

0 comments on commit e6edc56

Please sign in to comment.