Skip to content

Check all intersection constituents against index signatures in target #34546

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 4 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
31 changes: 25 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14479,7 +14479,7 @@ namespace ts {
* * Ternary.Maybe if they are related with assumptions of other relationships, or
* * Ternary.False if they are not related.
*/
function isRelatedTo(source: Type, target: Type, reportErrors = false, headMessage?: DiagnosticMessage, isApparentIntersectionConstituent?: boolean): Ternary {
function isRelatedTo(source: Type, target: Type, reportErrors = false, headMessage?: DiagnosticMessage, isIntersectionConstituent = false): Ternary {
if (isFreshLiteralType(source)) {
source = (<FreshableType>source).regularType;
}
Expand Down Expand Up @@ -14527,7 +14527,7 @@ namespace ts {
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;

const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
const isPerformingExcessPropertyChecks = !isApparentIntersectionConstituent && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
const isPerformingExcessPropertyChecks = !isIntersectionConstituent && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
if (isPerformingExcessPropertyChecks) {
const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
if (hasExcessProperties(<FreshObjectLiteralType>source, target, discriminantType, reportErrors)) {
Expand All @@ -14538,7 +14538,7 @@ namespace ts {
}
}

const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isApparentIntersectionConstituent &&
const isPerformingCommonPropertyChecks = relation !== comparableRelation && !isIntersectionConstituent &&
source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType &&
target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) &&
(getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source));
Expand All @@ -14559,7 +14559,6 @@ namespace ts {

let result = Ternary.False;
const saveErrorInfo = captureErrorCalculationState();
let isIntersectionConstituent = !!isApparentIntersectionConstituent;

// Note that these checks are specifically ordered to produce correct results. In particular,
// we need to deconstruct unions before intersections (because unions are always at the top),
Expand Down Expand Up @@ -14606,7 +14605,12 @@ namespace ts {
// breaking the intersection apart.
result = someTypeRelatedToType(<IntersectionType>source, target, /*reportErrors*/ false, /*isIntersectionConstituent*/ true);
}
if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) {
if (result && source.flags & TypeFlags.Intersection && target.flags & TypeFlags.Object && (getIndexInfoOfType(target, IndexKind.String) || getIndexInfoOfType(target, IndexKind.Number))) {
// When the source is an intersection and the target is an object type with an index signature we check
// that every object type constituent of the source relates to that index signature.
result &= eachTypeRelatedToIndexTypes(<IntersectionType>source, target, reportErrors);
}
else if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) {
if (result = recursiveTypeRelatedTo(source, target, reportErrors, isIntersectionConstituent)) {
resetErrorInfo(saveErrorInfo);
}
Expand Down Expand Up @@ -14926,6 +14930,21 @@ namespace ts {
return result;
}

function eachTypeRelatedToIndexTypes(source: IntersectionType, target: Type, reportErrors: boolean): Ternary {
let related = Ternary.True;
for (const sourceType of source.types) {
if (sourceType.flags & TypeFlags.Object) {
if (related) {
related &= indexTypesRelatedTo(sourceType, target, IndexKind.String, /*sourceIsPrimitive*/ false, reportErrors);
if (related) {
related &= indexTypesRelatedTo(sourceType, target, IndexKind.Number, /*sourceIsPrimitive*/ false, reportErrors);
}
}
}
}
return related;
}

function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, isIntersectionConstituent: boolean): Ternary {
if (sources.length !== targets.length && relation === identityRelation) {
return Ternary.False;
Expand Down Expand Up @@ -15928,7 +15947,7 @@ namespace ts {
if (prop.nameType && prop.nameType.flags & TypeFlags.UniqueESSymbol) {
continue;
}
if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) {
if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName) && !isInfinityOrNaNString(prop.escapedName)) {
const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors);
if (!related) {
if (reportErrors) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts(13,1): error TS2322: Type '{ a: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
Property 'b' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts(15,1): error TS2322: Type '{} & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
Property 'b' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts(17,1): error TS2322: Type '{ [key: number]: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
Property 'b' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts(19,1): error TS2322: Type '{ [key: string]: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
Property 'b' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts(29,10): error TS2339: Property 'b' does not exist on type '{ a: string; }'.
tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts(31,7): error TS2322: Type 'constr<{}, { [key: string]: { a: string; }; }>' is not assignable to type '{ [key: string]: { a: string; b: string; }; }'.
Index signatures are incompatible.
Property 'b' is missing in type '{ a: string; }' but required in type '{ a: string; b: string; }'.


==== tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts (6 errors) ====
declare let s1: { a: string } & { b: string };
declare let s2: { a: string } & { b: number };
declare let s3: { [K in never]: never } & { b: string };
declare let s4: { [K in never]: never } & { b: number };
declare let s5: { [key: number]: string } & { b: string };
declare let s6: { [key: number]: string } & { b: number };
declare let s7: { [key: string]: string } & { b: string };
declare let s8: { [key: string]: string } & { b: number };

declare let t1: { [key: string]: string };

t1 = s1;
t1 = s2; // Error
~~
!!! error TS2322: Type '{ a: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
!!! error TS2322: Property 'b' is incompatible with index signature.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
t1 = s3;
t1 = s4; // Error
~~
!!! error TS2322: Type '{} & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
!!! error TS2322: Property 'b' is incompatible with index signature.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
t1 = s5;
t1 = s6; // Error
~~
!!! error TS2322: Type '{ [key: number]: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
!!! error TS2322: Property 'b' is incompatible with index signature.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
t1 = s7;
t1 = s8; // Error
~~
!!! error TS2322: Type '{ [key: string]: string; } & { b: number; }' is not assignable to type '{ [key: string]: string; }'.
!!! error TS2322: Property 'b' is incompatible with index signature.
!!! error TS2322: Type 'number' is not assignable to type 'string'.

// Repro from #32484

type constr<Source, Tgt> = { [K in keyof Source]: string } & Pick<Tgt, Exclude<keyof Tgt, keyof Source>>;

type s = constr<{}, { [key: string]: { a: string } }>;

declare const q: s;
q["asd"].a.substr(1);
q["asd"].b; // Error
~
!!! error TS2339: Property 'b' does not exist on type '{ a: string; }'.

const d: { [key: string]: {a: string, b: string} } = q; // Error
~
!!! error TS2322: Type 'constr<{}, { [key: string]: { a: string; }; }>' is not assignable to type '{ [key: string]: { a: string; b: string; }; }'.
!!! error TS2322: Index signatures are incompatible.
!!! error TS2322: Property 'b' is missing in type '{ a: string; }' but required in type '{ a: string; b: string; }'.
!!! related TS2728 tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts:31:39: 'b' is declared here.

47 changes: 47 additions & 0 deletions tests/baselines/reference/intersectionsAndIndexSignatures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//// [intersectionsAndIndexSignatures.ts]
declare let s1: { a: string } & { b: string };
declare let s2: { a: string } & { b: number };
declare let s3: { [K in never]: never } & { b: string };
declare let s4: { [K in never]: never } & { b: number };
declare let s5: { [key: number]: string } & { b: string };
declare let s6: { [key: number]: string } & { b: number };
declare let s7: { [key: string]: string } & { b: string };
declare let s8: { [key: string]: string } & { b: number };

declare let t1: { [key: string]: string };

t1 = s1;
t1 = s2; // Error
t1 = s3;
t1 = s4; // Error
t1 = s5;
t1 = s6; // Error
t1 = s7;
t1 = s8; // Error

// Repro from #32484

type constr<Source, Tgt> = { [K in keyof Source]: string } & Pick<Tgt, Exclude<keyof Tgt, keyof Source>>;

type s = constr<{}, { [key: string]: { a: string } }>;

declare const q: s;
q["asd"].a.substr(1);
q["asd"].b; // Error

const d: { [key: string]: {a: string, b: string} } = q; // Error


//// [intersectionsAndIndexSignatures.js]
"use strict";
t1 = s1;
t1 = s2; // Error
t1 = s3;
t1 = s4; // Error
t1 = s5;
t1 = s6; // Error
t1 = s7;
t1 = s8; // Error
q["asd"].a.substr(1);
q["asd"].b; // Error
var d = q; // Error
118 changes: 118 additions & 0 deletions tests/baselines/reference/intersectionsAndIndexSignatures.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
=== tests/cases/conformance/types/intersection/intersectionsAndIndexSignatures.ts ===
declare let s1: { a: string } & { b: string };
>s1 : Symbol(s1, Decl(intersectionsAndIndexSignatures.ts, 0, 11))
>a : Symbol(a, Decl(intersectionsAndIndexSignatures.ts, 0, 17))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 0, 33))

declare let s2: { a: string } & { b: number };
>s2 : Symbol(s2, Decl(intersectionsAndIndexSignatures.ts, 1, 11))
>a : Symbol(a, Decl(intersectionsAndIndexSignatures.ts, 1, 17))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 1, 33))

declare let s3: { [K in never]: never } & { b: string };
>s3 : Symbol(s3, Decl(intersectionsAndIndexSignatures.ts, 2, 11))
>K : Symbol(K, Decl(intersectionsAndIndexSignatures.ts, 2, 19))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 2, 43))

declare let s4: { [K in never]: never } & { b: number };
>s4 : Symbol(s4, Decl(intersectionsAndIndexSignatures.ts, 3, 11))
>K : Symbol(K, Decl(intersectionsAndIndexSignatures.ts, 3, 19))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 3, 43))

declare let s5: { [key: number]: string } & { b: string };
>s5 : Symbol(s5, Decl(intersectionsAndIndexSignatures.ts, 4, 11))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 4, 19))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 4, 45))

declare let s6: { [key: number]: string } & { b: number };
>s6 : Symbol(s6, Decl(intersectionsAndIndexSignatures.ts, 5, 11))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 5, 19))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 5, 45))

declare let s7: { [key: string]: string } & { b: string };
>s7 : Symbol(s7, Decl(intersectionsAndIndexSignatures.ts, 6, 11))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 6, 19))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 6, 45))

declare let s8: { [key: string]: string } & { b: number };
>s8 : Symbol(s8, Decl(intersectionsAndIndexSignatures.ts, 7, 11))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 7, 19))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 7, 45))

declare let t1: { [key: string]: string };
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 9, 19))

t1 = s1;
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s1 : Symbol(s1, Decl(intersectionsAndIndexSignatures.ts, 0, 11))

t1 = s2; // Error
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s2 : Symbol(s2, Decl(intersectionsAndIndexSignatures.ts, 1, 11))

t1 = s3;
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s3 : Symbol(s3, Decl(intersectionsAndIndexSignatures.ts, 2, 11))

t1 = s4; // Error
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s4 : Symbol(s4, Decl(intersectionsAndIndexSignatures.ts, 3, 11))

t1 = s5;
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s5 : Symbol(s5, Decl(intersectionsAndIndexSignatures.ts, 4, 11))

t1 = s6; // Error
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s6 : Symbol(s6, Decl(intersectionsAndIndexSignatures.ts, 5, 11))

t1 = s7;
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s7 : Symbol(s7, Decl(intersectionsAndIndexSignatures.ts, 6, 11))

t1 = s8; // Error
>t1 : Symbol(t1, Decl(intersectionsAndIndexSignatures.ts, 9, 11))
>s8 : Symbol(s8, Decl(intersectionsAndIndexSignatures.ts, 7, 11))

// Repro from #32484

type constr<Source, Tgt> = { [K in keyof Source]: string } & Pick<Tgt, Exclude<keyof Tgt, keyof Source>>;
>constr : Symbol(constr, Decl(intersectionsAndIndexSignatures.ts, 18, 8))
>Source : Symbol(Source, Decl(intersectionsAndIndexSignatures.ts, 22, 12))
>Tgt : Symbol(Tgt, Decl(intersectionsAndIndexSignatures.ts, 22, 19))
>K : Symbol(K, Decl(intersectionsAndIndexSignatures.ts, 22, 30))
>Source : Symbol(Source, Decl(intersectionsAndIndexSignatures.ts, 22, 12))
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
>Tgt : Symbol(Tgt, Decl(intersectionsAndIndexSignatures.ts, 22, 19))
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
>Tgt : Symbol(Tgt, Decl(intersectionsAndIndexSignatures.ts, 22, 19))
>Source : Symbol(Source, Decl(intersectionsAndIndexSignatures.ts, 22, 12))

type s = constr<{}, { [key: string]: { a: string } }>;
>s : Symbol(s, Decl(intersectionsAndIndexSignatures.ts, 22, 105))
>constr : Symbol(constr, Decl(intersectionsAndIndexSignatures.ts, 18, 8))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 24, 23))
>a : Symbol(a, Decl(intersectionsAndIndexSignatures.ts, 24, 38))

declare const q: s;
>q : Symbol(q, Decl(intersectionsAndIndexSignatures.ts, 26, 13))
>s : Symbol(s, Decl(intersectionsAndIndexSignatures.ts, 22, 105))

q["asd"].a.substr(1);
>q["asd"].a.substr : Symbol(String.substr, Decl(lib.es5.d.ts, --, --))
>q["asd"].a : Symbol(a, Decl(intersectionsAndIndexSignatures.ts, 24, 38))
>q : Symbol(q, Decl(intersectionsAndIndexSignatures.ts, 26, 13))
>a : Symbol(a, Decl(intersectionsAndIndexSignatures.ts, 24, 38))
>substr : Symbol(String.substr, Decl(lib.es5.d.ts, --, --))

q["asd"].b; // Error
>q : Symbol(q, Decl(intersectionsAndIndexSignatures.ts, 26, 13))

const d: { [key: string]: {a: string, b: string} } = q; // Error
>d : Symbol(d, Decl(intersectionsAndIndexSignatures.ts, 30, 5))
>key : Symbol(key, Decl(intersectionsAndIndexSignatures.ts, 30, 12))
>a : Symbol(a, Decl(intersectionsAndIndexSignatures.ts, 30, 27))
>b : Symbol(b, Decl(intersectionsAndIndexSignatures.ts, 30, 37))
>q : Symbol(q, Decl(intersectionsAndIndexSignatures.ts, 26, 13))

Loading