Skip to content

Fix relations for instantiations of same generic signature #31029

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

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 29 additions & 24 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10950,11 +10950,16 @@ namespace ts {
isInJSFile(signature.declaration));
}

function getErasedConstraint(type: Type, typeParameters: readonly TypeParameter[]): Type | undefined {
const constraint = getConstraintOfType(type);
return constraint && contains(typeParameters, constraint) ? getErasedConstraint(constraint, typeParameters) : constraint;
}

function getBaseSignature(signature: Signature) {
const typeParameters = signature.typeParameters;
if (typeParameters) {
const typeEraser = createTypeEraser(typeParameters);
const baseConstraints = map(typeParameters, tp => instantiateType(getBaseConstraintOfType(tp), typeEraser) || unknownType);
const baseConstraints = map(typeParameters, tp => instantiateType(getErasedConstraint(tp, typeParameters), typeEraser) || unknownType);
return instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true);
}
return signature;
Expand Down Expand Up @@ -14710,7 +14715,7 @@ namespace ts {
// with respect to T.
const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType));
const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType));
const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) &&
const callbacks = sourceSig && targetSig &&
(getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable);
let related = callbacks ?
compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) :
Expand Down Expand Up @@ -14745,10 +14750,11 @@ namespace ts {

// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
const targetTypePredicate = getTypePredicateOfSignature(target);
if (targetTypePredicate) {
const sourceTypePredicate = getTypePredicateOfSignature(source);
const sourceTypePredicate = getTypePredicateOfSignature(source);
if (targetTypePredicate && (sourceTypePredicate || !(checkMode & SignatureCheckMode.BivariantCallback))) {
if (sourceTypePredicate) {
result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes);
result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypePredicateRelatedTo(targetTypePredicate, sourceTypePredicate, /*reportErrors*/ false, /*errorReporter*/ undefined, compareTypes) ||
compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes);
}
else if (isIdentifierTypePredicate(targetTypePredicate)) {
if (reportErrors) {
Expand Down Expand Up @@ -16566,34 +16572,33 @@ namespace ts {
const saveErrorInfo = captureErrorCalculationState();
const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn;

if (getObjectFlags(source) & ObjectFlags.Instantiated && getObjectFlags(target) & ObjectFlags.Instantiated && source.symbol === target.symbol) {
// We have instantiations of the same anonymous type (which typically will be the type of a
// method). Simply do a pairwise comparison of the signatures in the two signature lists instead
// of the much more expensive N * M comparison matrix we explore below. We erase type parameters
// as they are known to always be the same.
const sameSignatureInstantiations = getObjectFlags(source) & ObjectFlags.Instantiated && getObjectFlags(target) & ObjectFlags.Instantiated && source.symbol === target.symbol;
if (sameSignatureInstantiations || sourceSignatures.length === 1 && targetSignatures.length === 1) {
// We have instantiations of the same anonymous type (which typically will be the type of a method)
// or we have non-overloaded signatures. Simply do a pairwise comparison of the signatures in the
// two signature lists instead of the much more expensive N * M comparison matrix we explore below.
const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks;
for (let i = 0; i < targetSignatures.length; i++) {
const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i]));
const s = sourceSignatures[i];
const t = targetSignatures[i];
// We erase type parameters for the comparable relation or when strict checks are disabled.
// Otherwise, when we have instantiations of the same anonymous type, we instantiate target
// type parameters to their constraints because the type parameters are known to be the same.
const effectiveSource = eraseGenerics ? getErasedSignature(s) : s;
const effectiveTarget = eraseGenerics ? getErasedSignature(t) : sameSignatureInstantiations ? getBaseSignature(t) : t;
const related = signatureRelatedTo(effectiveSource, effectiveTarget, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i]));
if (!related) {
return Ternary.False;
}
result &= related;
}
}
else if (sourceSignatures.length === 1 && targetSignatures.length === 1) {
// For simple functions (functions with a single signature) we only erase type parameters for
// the comparable relation. Otherwise, if the source signature is generic, we instantiate it
// in the context of the target signature before checking the relationship. Ideally we'd do
// this regardless of the number of signatures, but the potential costs are prohibitive due
// to the quadratic nature of the logic below.
const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks;
result = signatureRelatedTo(sourceSignatures[0], targetSignatures[0], eraseGenerics, reportErrors, incompatibleReporter(sourceSignatures[0], targetSignatures[0]));
}
else {
outer: for (const t of targetSignatures) {
// Only elaborate errors from the first failure
let shouldElaborateErrors = reportErrors;
for (const s of sourceSignatures) {
const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t));
const related = signatureRelatedTo(getErasedSignature(s), getErasedSignature(t), shouldElaborateErrors, incompatibleReporter(s, t));
if (related) {
result &= related;
resetErrorInfo(saveErrorInfo);
Expand Down Expand Up @@ -16630,9 +16635,9 @@ namespace ts {
/**
* See signatureAssignableTo, compareSignaturesIdentical
*/
function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target,
relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary {
return compareSignaturesRelated(source, target, relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0,
reportErrors, reportError, incompatibleReporter, isRelatedTo, reportUnreliableMarkers);
}

function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,7 @@ declare module Immutable {
>Seq : typeof Seq

function isSeq(maybeSeq: any): maybeSeq is Seq.Indexed<any> | Seq.Keyed<any, any>;
>isSeq : (maybeSeq: any) => maybeSeq is Indexed<any> | Keyed<any, any>
>isSeq : (maybeSeq: any) => maybeSeq is Keyed<any, any> | Indexed<any>
>maybeSeq : any
>Seq : any
>Seq : any
Expand Down
59 changes: 58 additions & 1 deletion tests/baselines/reference/keyofAndIndexedAccess2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,27 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(67,3): error TS232
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(68,3): error TS2322: Type '123' is not assignable to type 'T[K]'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(108,5): error TS2322: Type '123' is not assignable to type 'Type[K]'.
Type '123' is not assignable to type 'never'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(126,1): error TS2322: Type 'Demo<{ b: string; }>' is not assignable to type 'Demo<{ a: number; }>'.
Types of parameters 'key' and 'key' are incompatible.
Type '"a"' is not assignable to type '"b"'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(127,1): error TS2322: Type 'Demo<{ a: number; b: string; }>' is not assignable to type 'Demo<{ a: number; }>'.
Property 'b' is missing in type '{ a: number; }' but required in type '{ a: number; b: string; }'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(128,1): error TS2322: Type 'Demo<{ a: number; }>' is not assignable to type 'Demo<{ b: string; }>'.
Types of parameters 'key' and 'key' are incompatible.
Type '"b"' is not assignable to type '"a"'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(129,1): error TS2322: Type 'Demo<{ a: number; b: string; }>' is not assignable to type 'Demo<{ b: string; }>'.
Property 'a' is missing in type '{ b: string; }' but required in type '{ a: number; b: string; }'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(130,1): error TS2322: Type 'Demo<{ a: number; }>' is not assignable to type 'Demo<{ a: number; b: string; }>'.
Types of parameters 'key' and 'key' are incompatible.
Type '"a" | "b"' is not assignable to type '"a"'.
Type '"b"' is not assignable to type '"a"'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(131,1): error TS2322: Type 'Demo<{ b: string; }>' is not assignable to type 'Demo<{ a: number; b: string; }>'.
Types of parameters 'key' and 'key' are incompatible.
Type '"a" | "b"' is not assignable to type '"b"'.
Type '"a"' is not assignable to type '"b"'.


==== tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts (23 errors) ====
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts (29 errors) ====
function f1(obj: { a: number, b: 0 | 1, c: string }, k0: 'a', k1: 'a' | 'b', k2: 'a' | 'b' | 'c') {
obj[k0] = 1;
obj[k0] = 2;
Expand Down Expand Up @@ -197,6 +215,45 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(108,5): error TS23
type A<T> = { [Q in { [P in keyof T]: P; }[keyof T]]: T[Q]; };
type B<T, V> = A<{ [Q in keyof T]: StrictExclude<B<T[Q], V>, {}>; }>;

// Repro from 31006

type Demo<T> = <K extends keyof T>(key: K, val: T[K]) => void;

declare let da: Demo<{ a: number }>;
declare let db: Demo<{ b: string }>;
declare let dc: Demo<{ a: number, b: string }>;

da = db; // Error
~~
!!! error TS2322: Type 'Demo<{ b: string; }>' is not assignable to type 'Demo<{ a: number; }>'.
!!! error TS2322: Types of parameters 'key' and 'key' are incompatible.
!!! error TS2322: Type '"a"' is not assignable to type '"b"'.
da = dc;
~~
!!! error TS2322: Type 'Demo<{ a: number; b: string; }>' is not assignable to type 'Demo<{ a: number; }>'.
!!! error TS2322: Property 'b' is missing in type '{ a: number; }' but required in type '{ a: number; b: string; }'.
db = da; // Error
~~
!!! error TS2322: Type 'Demo<{ a: number; }>' is not assignable to type 'Demo<{ b: string; }>'.
!!! error TS2322: Types of parameters 'key' and 'key' are incompatible.
!!! error TS2322: Type '"b"' is not assignable to type '"a"'.
db = dc;
~~
!!! error TS2322: Type 'Demo<{ a: number; b: string; }>' is not assignable to type 'Demo<{ b: string; }>'.
!!! error TS2322: Property 'a' is missing in type '{ b: string; }' but required in type '{ a: number; b: string; }'.
dc = da; // Error
~~
!!! error TS2322: Type 'Demo<{ a: number; }>' is not assignable to type 'Demo<{ a: number; b: string; }>'.
!!! error TS2322: Types of parameters 'key' and 'key' are incompatible.
!!! error TS2322: Type '"a" | "b"' is not assignable to type '"a"'.
!!! error TS2322: Type '"b"' is not assignable to type '"a"'.
dc = db; // Error
~~
!!! error TS2322: Type 'Demo<{ b: string; }>' is not assignable to type 'Demo<{ a: number; b: string; }>'.
!!! error TS2322: Types of parameters 'key' and 'key' are incompatible.
!!! error TS2322: Type '"a" | "b"' is not assignable to type '"b"'.
!!! error TS2322: Type '"a"' is not assignable to type '"b"'.

// Repros from #30938

function fn<T extends {elements: Array<string>} | {elements: Array<number>}>(param: T, cb: (element: T['elements'][number]) => void) {
Expand Down
21 changes: 21 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccess2.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ type StrictExclude<T, U> = T extends StrictExtract<T, U> ? never : T;
type A<T> = { [Q in { [P in keyof T]: P; }[keyof T]]: T[Q]; };
type B<T, V> = A<{ [Q in keyof T]: StrictExclude<B<T[Q], V>, {}>; }>;

// Repro from 31006

type Demo<T> = <K extends keyof T>(key: K, val: T[K]) => void;

declare let da: Demo<{ a: number }>;
declare let db: Demo<{ b: string }>;
declare let dc: Demo<{ a: number, b: string }>;

da = db; // Error
da = dc;
db = da; // Error
db = dc;
dc = da; // Error
dc = db; // Error

// Repros from #30938

function fn<T extends {elements: Array<string>} | {elements: Array<number>}>(param: T, cb: (element: T['elements'][number]) => void) {
Expand Down Expand Up @@ -240,6 +255,12 @@ export function getEntity(id, state) {
function get123() {
return 123; // Error
}
da = db; // Error
da = dc;
db = da; // Error
db = dc;
dc = da; // Error
dc = db; // Error
// Repros from #30938
function fn(param, cb) {
cb(param.elements[0]);
Expand Down
Loading