Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Fixed excess and common property checks with NoInfer
  • Loading branch information
Andarist committed Mar 8, 2024
commit bcbd4196adb2becf34eb846060acfef007a23905
9 changes: 8 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23756,6 +23756,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 &&
resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional));
}
if (isNoInferType(type)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to erase all substitution types (to their base types) instead of just NoInfer types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed out the requested change but without any new tests.

I've tried a bunch of things based on my intuition in an attempt to write some tests for this change and nothing - from my attempts - managed to hit those lines with a non-NoInfer substitution type. So I went to recheck if I could observe some cases like this using the existing test suite:

  • isExcessPropertyCheckTarget/isKnownProperty didn't observe such substitution types at all
  • isWeakType has observed it in deeplyNestedMappedTypes and conditionalTypeDoesntSpinForever. Those are complex enough that figuring out a test case based solely on them might take me a longer moment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not going to be easy to come up with a case where it matters and I'm fine going without.

return isWeakType((type as SubstitutionType).baseType);
}
if (type.flags & TypeFlags.Intersection) {
return every((type as IntersectionType).types, isWeakType);
}
Expand Down Expand Up @@ -32464,7 +32467,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return true;
}
}
else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
if (isNoInferType(targetType)) {
return isKnownProperty((targetType as SubstitutionType).baseType, name, isComparingJsxAttributes);
}
if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
for (const t of (targetType as UnionOrIntersectionType).types) {
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
return true;
Expand All @@ -32477,6 +32483,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function isExcessPropertyCheckTarget(type: Type): boolean {
return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) ||
type.flags & TypeFlags.NonPrimitive ||
isNoInferType(type) && isExcessPropertyCheckTarget((type as SubstitutionType).baseType) ||
type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) ||
type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget));
}
Expand Down
36 changes: 36 additions & 0 deletions tests/baselines/reference/noInferCommonPropertyCheck1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
noInferCommonPropertyCheck1.ts(7,20): error TS2559: Type '{ x: string; }' has no properties in common with type 'NoInfer<Partial<{ a: unknown; b: unknown; }>> & { prop?: unknown; }'.
noInferCommonPropertyCheck1.ts(15,33): error TS2559: Type '{ x: string; }' has no properties in common with type 'NoInfer<Partial<{ a: unknown; b: unknown; }>> & NoInfer<Partial<{ c: unknown; d: unknown; }>>'.
noInferCommonPropertyCheck1.ts(23,33): error TS2559: Type '{ x: string; }' has no properties in common with type 'Partial<{ a: unknown; b: unknown; }> & Partial<{ c: unknown; d: unknown; }>'.


==== noInferCommonPropertyCheck1.ts (3 errors) ====
declare const partialObj1: Partial<{ a: unknown; b: unknown }>;
declare const partialObj2: Partial<{ c: unknown; d: unknown }>;
declare const someObj1: { x: string };

declare function test1<T>(a: T, b: NoInfer<T> & { prop?: unknown }): void;

test1(partialObj1, someObj1);
~~~~~~~~
!!! error TS2559: Type '{ x: string; }' has no properties in common with type 'NoInfer<Partial<{ a: unknown; b: unknown; }>> & { prop?: unknown; }'.

declare function test2<T1, T2>(
a: T1,
b: T2,
c: NoInfer<T1> & NoInfer<T2>,
): void;

test2(partialObj1, partialObj2, someObj1);
~~~~~~~~
!!! error TS2559: Type '{ x: string; }' has no properties in common with type 'NoInfer<Partial<{ a: unknown; b: unknown; }>> & NoInfer<Partial<{ c: unknown; d: unknown; }>>'.

declare function test3<T1, T2>(
a: T1,
b: T2,
c: NoInfer<T1 & T2>,
): void;

test3(partialObj1, partialObj2, someObj1);
~~~~~~~~
!!! error TS2559: Type '{ x: string; }' has no properties in common with type 'Partial<{ a: unknown; b: unknown; }> & Partial<{ c: unknown; d: unknown; }>'.

89 changes: 89 additions & 0 deletions tests/baselines/reference/noInferCommonPropertyCheck1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//// [tests/cases/compiler/noInferCommonPropertyCheck1.ts] ////

=== noInferCommonPropertyCheck1.ts ===
declare const partialObj1: Partial<{ a: unknown; b: unknown }>;
>partialObj1 : Symbol(partialObj1, Decl(noInferCommonPropertyCheck1.ts, 0, 13))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(noInferCommonPropertyCheck1.ts, 0, 36))
>b : Symbol(b, Decl(noInferCommonPropertyCheck1.ts, 0, 48))

declare const partialObj2: Partial<{ c: unknown; d: unknown }>;
>partialObj2 : Symbol(partialObj2, Decl(noInferCommonPropertyCheck1.ts, 1, 13))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>c : Symbol(c, Decl(noInferCommonPropertyCheck1.ts, 1, 36))
>d : Symbol(d, Decl(noInferCommonPropertyCheck1.ts, 1, 48))

declare const someObj1: { x: string };
>someObj1 : Symbol(someObj1, Decl(noInferCommonPropertyCheck1.ts, 2, 13))
>x : Symbol(x, Decl(noInferCommonPropertyCheck1.ts, 2, 25))

declare function test1<T>(a: T, b: NoInfer<T> & { prop?: unknown }): void;
>test1 : Symbol(test1, Decl(noInferCommonPropertyCheck1.ts, 2, 38))
>T : Symbol(T, Decl(noInferCommonPropertyCheck1.ts, 4, 23))
>a : Symbol(a, Decl(noInferCommonPropertyCheck1.ts, 4, 26))
>T : Symbol(T, Decl(noInferCommonPropertyCheck1.ts, 4, 23))
>b : Symbol(b, Decl(noInferCommonPropertyCheck1.ts, 4, 31))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(noInferCommonPropertyCheck1.ts, 4, 23))
>prop : Symbol(prop, Decl(noInferCommonPropertyCheck1.ts, 4, 49))

test1(partialObj1, someObj1);
>test1 : Symbol(test1, Decl(noInferCommonPropertyCheck1.ts, 2, 38))
>partialObj1 : Symbol(partialObj1, Decl(noInferCommonPropertyCheck1.ts, 0, 13))
>someObj1 : Symbol(someObj1, Decl(noInferCommonPropertyCheck1.ts, 2, 13))

declare function test2<T1, T2>(
>test2 : Symbol(test2, Decl(noInferCommonPropertyCheck1.ts, 6, 29))
>T1 : Symbol(T1, Decl(noInferCommonPropertyCheck1.ts, 8, 23))
>T2 : Symbol(T2, Decl(noInferCommonPropertyCheck1.ts, 8, 26))

a: T1,
>a : Symbol(a, Decl(noInferCommonPropertyCheck1.ts, 8, 31))
>T1 : Symbol(T1, Decl(noInferCommonPropertyCheck1.ts, 8, 23))

b: T2,
>b : Symbol(b, Decl(noInferCommonPropertyCheck1.ts, 9, 8))
>T2 : Symbol(T2, Decl(noInferCommonPropertyCheck1.ts, 8, 26))

c: NoInfer<T1> & NoInfer<T2>,
>c : Symbol(c, Decl(noInferCommonPropertyCheck1.ts, 10, 8))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T1 : Symbol(T1, Decl(noInferCommonPropertyCheck1.ts, 8, 23))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T2 : Symbol(T2, Decl(noInferCommonPropertyCheck1.ts, 8, 26))

): void;

test2(partialObj1, partialObj2, someObj1);
>test2 : Symbol(test2, Decl(noInferCommonPropertyCheck1.ts, 6, 29))
>partialObj1 : Symbol(partialObj1, Decl(noInferCommonPropertyCheck1.ts, 0, 13))
>partialObj2 : Symbol(partialObj2, Decl(noInferCommonPropertyCheck1.ts, 1, 13))
>someObj1 : Symbol(someObj1, Decl(noInferCommonPropertyCheck1.ts, 2, 13))

declare function test3<T1, T2>(
>test3 : Symbol(test3, Decl(noInferCommonPropertyCheck1.ts, 14, 42))
>T1 : Symbol(T1, Decl(noInferCommonPropertyCheck1.ts, 16, 23))
>T2 : Symbol(T2, Decl(noInferCommonPropertyCheck1.ts, 16, 26))

a: T1,
>a : Symbol(a, Decl(noInferCommonPropertyCheck1.ts, 16, 31))
>T1 : Symbol(T1, Decl(noInferCommonPropertyCheck1.ts, 16, 23))

b: T2,
>b : Symbol(b, Decl(noInferCommonPropertyCheck1.ts, 17, 8))
>T2 : Symbol(T2, Decl(noInferCommonPropertyCheck1.ts, 16, 26))

c: NoInfer<T1 & T2>,
>c : Symbol(c, Decl(noInferCommonPropertyCheck1.ts, 18, 8))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T1 : Symbol(T1, Decl(noInferCommonPropertyCheck1.ts, 16, 23))
>T2 : Symbol(T2, Decl(noInferCommonPropertyCheck1.ts, 16, 26))

): void;

test3(partialObj1, partialObj2, someObj1);
>test3 : Symbol(test3, Decl(noInferCommonPropertyCheck1.ts, 14, 42))
>partialObj1 : Symbol(partialObj1, Decl(noInferCommonPropertyCheck1.ts, 0, 13))
>partialObj2 : Symbol(partialObj2, Decl(noInferCommonPropertyCheck1.ts, 1, 13))
>someObj1 : Symbol(someObj1, Decl(noInferCommonPropertyCheck1.ts, 2, 13))

71 changes: 71 additions & 0 deletions tests/baselines/reference/noInferCommonPropertyCheck1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//// [tests/cases/compiler/noInferCommonPropertyCheck1.ts] ////

=== noInferCommonPropertyCheck1.ts ===
declare const partialObj1: Partial<{ a: unknown; b: unknown }>;
>partialObj1 : Partial<{ a: unknown; b: unknown; }>
>a : unknown
>b : unknown

declare const partialObj2: Partial<{ c: unknown; d: unknown }>;
>partialObj2 : Partial<{ c: unknown; d: unknown; }>
>c : unknown
>d : unknown

declare const someObj1: { x: string };
>someObj1 : { x: string; }
>x : string

declare function test1<T>(a: T, b: NoInfer<T> & { prop?: unknown }): void;
>test1 : <T>(a: T, b: NoInfer<T> & { prop?: unknown;}) => void
>a : T
>b : NoInfer<T> & { prop?: unknown; }
>prop : unknown

test1(partialObj1, someObj1);
>test1(partialObj1, someObj1) : void
>test1 : <T>(a: T, b: NoInfer<T> & { prop?: unknown; }) => void
>partialObj1 : Partial<{ a: unknown; b: unknown; }>
>someObj1 : { x: string; }

declare function test2<T1, T2>(
>test2 : <T1, T2>(a: T1, b: T2, c: NoInfer<T1> & NoInfer<T2>) => void

a: T1,
>a : T1

b: T2,
>b : T2

c: NoInfer<T1> & NoInfer<T2>,
>c : NoInfer<T1> & NoInfer<T2>

): void;

test2(partialObj1, partialObj2, someObj1);
>test2(partialObj1, partialObj2, someObj1) : void
>test2 : <T1, T2>(a: T1, b: T2, c: NoInfer<T1> & NoInfer<T2>) => void
>partialObj1 : Partial<{ a: unknown; b: unknown; }>
>partialObj2 : Partial<{ c: unknown; d: unknown; }>
>someObj1 : { x: string; }

declare function test3<T1, T2>(
>test3 : <T1, T2>(a: T1, b: T2, c: NoInfer<T1 & T2>) => void

a: T1,
>a : T1

b: T2,
>b : T2

c: NoInfer<T1 & T2>,
>c : NoInfer<T1 & T2>

): void;

test3(partialObj1, partialObj2, someObj1);
>test3(partialObj1, partialObj2, someObj1) : void
>test3 : <T1, T2>(a: T1, b: T2, c: NoInfer<T1 & T2>) => void
>partialObj1 : Partial<{ a: unknown; b: unknown; }>
>partialObj2 : Partial<{ c: unknown; d: unknown; }>
>someObj1 : { x: string; }

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
noInferUnionExcessPropertyCheck1.ts(7,33): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type 'NoInfer<{ x: string; }> | (() => NoInfer<{ x: string; }>)'.
noInferUnionExcessPropertyCheck1.ts(15,33): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type 'NoInfer<{ x: string; }> | NoInfer<() => { x: string; }>'.
noInferUnionExcessPropertyCheck1.ts(23,33): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: string; } | (() => { x: string; })'.


==== noInferUnionExcessPropertyCheck1.ts (3 errors) ====
declare function test1<T extends { x: string }>(
a: T,
b: NoInfer<T> | (() => NoInfer<T>),
): void;

test1({ x: "foo" }, { x: "bar" }); // no error
test1({ x: "foo" }, { x: "bar", y: 42 }); // epc error
~
!!! error TS2353: Object literal may only specify known properties, and 'y' does not exist in type 'NoInfer<{ x: string; }> | (() => NoInfer<{ x: string; }>)'.

declare function test2<T extends { x: string }>(
a: T,
b: NoInfer<T> | NoInfer<() => T>,
): void;

test2({ x: "foo" }, { x: "bar" }); // no error
test2({ x: "foo" }, { x: "bar", y: 42 }); // epc error
~
!!! error TS2353: Object literal may only specify known properties, and 'y' does not exist in type 'NoInfer<{ x: string; }> | NoInfer<() => { x: string; }>'.

declare function test3<T extends { x: string }>(
a: T,
b: NoInfer<T | (() => T)>,
): void;

test3({ x: "foo" }, { x: "bar" }); // no error
test3({ x: "foo" }, { x: "bar", y: 42 }); // epc error
~
!!! error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: string; } | (() => { x: string; })'.

89 changes: 89 additions & 0 deletions tests/baselines/reference/noInferUnionExcessPropertyCheck1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//// [tests/cases/compiler/noInferUnionExcessPropertyCheck1.ts] ////

=== noInferUnionExcessPropertyCheck1.ts ===
declare function test1<T extends { x: string }>(
>test1 : Symbol(test1, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 0))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 23))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 34))

a: T,
>a : Symbol(a, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 48))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 23))

b: NoInfer<T> | (() => NoInfer<T>),
>b : Symbol(b, Decl(noInferUnionExcessPropertyCheck1.ts, 1, 7))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 23))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 23))

): void;

test1({ x: "foo" }, { x: "bar" }); // no error
>test1 : Symbol(test1, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 0))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 5, 7))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 5, 21))

test1({ x: "foo" }, { x: "bar", y: 42 }); // epc error
>test1 : Symbol(test1, Decl(noInferUnionExcessPropertyCheck1.ts, 0, 0))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 6, 7))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 6, 21))
>y : Symbol(y, Decl(noInferUnionExcessPropertyCheck1.ts, 6, 31))

declare function test2<T extends { x: string }>(
>test2 : Symbol(test2, Decl(noInferUnionExcessPropertyCheck1.ts, 6, 41))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 8, 23))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 8, 34))

a: T,
>a : Symbol(a, Decl(noInferUnionExcessPropertyCheck1.ts, 8, 48))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 8, 23))

b: NoInfer<T> | NoInfer<() => T>,
>b : Symbol(b, Decl(noInferUnionExcessPropertyCheck1.ts, 9, 7))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 8, 23))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 8, 23))

): void;

test2({ x: "foo" }, { x: "bar" }); // no error
>test2 : Symbol(test2, Decl(noInferUnionExcessPropertyCheck1.ts, 6, 41))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 13, 7))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 13, 21))

test2({ x: "foo" }, { x: "bar", y: 42 }); // epc error
>test2 : Symbol(test2, Decl(noInferUnionExcessPropertyCheck1.ts, 6, 41))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 14, 7))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 14, 21))
>y : Symbol(y, Decl(noInferUnionExcessPropertyCheck1.ts, 14, 31))

declare function test3<T extends { x: string }>(
>test3 : Symbol(test3, Decl(noInferUnionExcessPropertyCheck1.ts, 14, 41))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 16, 23))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 16, 34))

a: T,
>a : Symbol(a, Decl(noInferUnionExcessPropertyCheck1.ts, 16, 48))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 16, 23))

b: NoInfer<T | (() => T)>,
>b : Symbol(b, Decl(noInferUnionExcessPropertyCheck1.ts, 17, 7))
>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 16, 23))
>T : Symbol(T, Decl(noInferUnionExcessPropertyCheck1.ts, 16, 23))

): void;

test3({ x: "foo" }, { x: "bar" }); // no error
>test3 : Symbol(test3, Decl(noInferUnionExcessPropertyCheck1.ts, 14, 41))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 21, 7))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 21, 21))

test3({ x: "foo" }, { x: "bar", y: 42 }); // epc error
>test3 : Symbol(test3, Decl(noInferUnionExcessPropertyCheck1.ts, 14, 41))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 22, 7))
>x : Symbol(x, Decl(noInferUnionExcessPropertyCheck1.ts, 22, 21))
>y : Symbol(y, Decl(noInferUnionExcessPropertyCheck1.ts, 22, 31))

Loading