Skip to content

Bugfix/union excess property check #54823

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
49 changes: 40 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20876,17 +20876,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
let reducedTarget = target;
let checkTypes: Type[] | undefined;
const excessProperties: Set<any> = new Set();
const assignableProperties: Set<any> = new Set();
if (target.flags & TypeFlags.Union) {
reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType);
checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget];
}
// Report error in terms of object types in the target as those are the only ones
// we check in isKnownProperty.
const excessPropertyTarget = filterType(reducedTarget, isExcessPropertyCheckTarget) as UnionOrIntersectionType;
if (target.flags & TypeFlags.Union) {
for (const t of excessPropertyTarget.types) {
let typeCovered = true;
for (const prop of getPropertiesOfType(source)) {
if (!isKnownProperty(t, prop.escapedName, isComparingJsxAttributes)) {
typeCovered = false;
excessProperties.add(prop);
}
else {
assignableProperties.add(prop);
}
}
if (typeCovered) {
// Whichever type in the union covers all assigned properties will also
return false;
}
}
}
for (const prop of getPropertiesOfType(source)) {
if (assignableProperties.has(prop)) {
continue;
}
if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) {
if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) {
if (reportErrors) {
// Report error in terms of object types in the target as those are the only ones
// we check in isKnownProperty.
const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget);

// We know *exactly* where things went wrong when comparing the types.
// Use this property as the error node as this will be more helpful in
// reasoning about what went wrong.
Expand All @@ -20900,13 +20924,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
errorNode = prop.valueDeclaration.name;
}
const propName = symbolToString(prop);
const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget);
const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, excessPropertyTarget);
const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined;
if (suggestion) {
reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion);
reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(excessPropertyTarget), suggestion);
}
else {
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget));
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(excessPropertyTarget));
}
}
else {
Expand All @@ -20921,16 +20945,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
errorNode = name;

if (isIdentifier(name)) {
suggestion = getSuggestionForNonexistentProperty(name, errorTarget);
suggestion = getSuggestionForNonexistentProperty(name, excessPropertyTarget);
}
}
if (suggestion !== undefined) {
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2,
symbolToString(prop), typeToString(errorTarget), suggestion);
symbolToString(prop), typeToString(excessPropertyTarget), suggestion);
}
else {
reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
symbolToString(prop), typeToString(errorTarget));
symbolToString(prop), typeToString(excessPropertyTarget));
}
}
}
Expand All @@ -20944,6 +20968,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
}
const undecidedProperties = [...excessProperties].filter((p: any) => assignableProperties.has(p));
if (undecidedProperties.length) {
if (reportErrors) {
reportError(Diagnostics.Properties_1_can_t_be_assigned_to_any_type_in_union_2_and_make_0_unassignable, typeToString(source), undecidedProperties.join(", "), typeToString(excessPropertyTarget));
}
return true;
}
return false;
}

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3659,6 +3659,10 @@
"category": "Error",
"code": 2854
},
"Properties '{1}' can't be assigned to any type in union '{2}' and make '{0}' unassignable": {
"category": "Error",
"code": 2855
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
62 changes: 62 additions & 0 deletions tests/baselines/reference/unionTypeExcessPropertyCheck.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
unionTypeExcessPropertyCheck.ts(11,9): error TS2322: Type '{ b: string; c: string; }' is not assignable to type 'AC'.
Object literal may only specify known properties, and 'b' does not exist in type 'AC'.
unionTypeExcessPropertyCheck.ts(17,9): error TS2322: Type '{ b: string; c: string; }' is not assignable to type 'B'.
Object literal may only specify known properties, and 'c' does not exist in type 'B'.
unionTypeExcessPropertyCheck.ts(27,5): error TS2322: Type '{ b: string; x: string; }' is not assignable to type 'AC | B'.
Object literal may only specify known properties, and 'x' does not exist in type 'AC | B'.
Excess properties detected in Object literal '{ b: string; x: string; }' combination of properties '[object Object]' make type 'AC | B' undeducible
unionTypeExcessPropertyCheck.ts(33,5): error TS2322: Type '{ a: string; c: string; x: string; }' is not assignable to type 'AC | B'.
Object literal may only specify known properties, and 'x' does not exist in type 'AC | B'.
Excess properties detected in Object literal '{ a: string; c: string; x: string; }' combination of properties '[object Object], [object Object]' make type 'AC | B' undeducible


==== unionTypeExcessPropertyCheck.ts (4 errors) ====
type AC = {
a: string,
c: string
};
type B = {
b: string
};

// Fails correctly as `b` is not in `AC`
const ac_b: AC = {
b: '',
~
!!! error TS2322: Type '{ b: string; c: string; }' is not assignable to type 'AC'.
!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'AC'.
c: ''
};
// Fails correctly as `c` is not in `B`
const b_c: B = {
b: '',
c: ''
~
!!! error TS2322: Type '{ b: string; c: string; }' is not assignable to type 'B'.
!!! error TS2322: Object literal may only specify known properties, and 'c' does not exist in type 'B'.
};
// Should fail because `c` is not in `B` while `b` is not in `AB`, but works instead
const acb_bc: AC|B = {
b: '',
c: ''
};
// Fails correctly as `x` in in neither `AC` nor `B`
const acb_bx: AC|B = {
b: '',
x: ''
~
!!! error TS2322: Type '{ b: string; x: string; }' is not assignable to type 'AC | B'.
!!! error TS2322: Object literal may only specify known properties, and 'x' does not exist in type 'AC | B'.
!!! error TS2322: Excess properties detected in Object literal '{ b: string; x: string; }' combination of properties '[object Object]' make type 'AC | B' undeducible
};
// Fails correctly as `x` in in neither `AC` nor `B`
const acb_acx: AC|B = {
a: '',
c: '',
x: ''
~
!!! error TS2322: Type '{ a: string; c: string; x: string; }' is not assignable to type 'AC | B'.
!!! error TS2322: Object literal may only specify known properties, and 'x' does not exist in type 'AC | B'.
!!! error TS2322: Excess properties detected in Object literal '{ a: string; c: string; x: string; }' combination of properties '[object Object], [object Object]' make type 'AC | B' undeducible
};

66 changes: 66 additions & 0 deletions tests/baselines/reference/unionTypeExcessPropertyCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//// [tests/cases/conformance/types/union/unionTypeExcessPropertyCheck.ts] ////

//// [unionTypeExcessPropertyCheck.ts]
type AC = {
a: string,
c: string
};
type B = {
b: string
};

// Fails correctly as `b` is not in `AC`
const ac_b: AC = {
b: '',
c: ''
};
// Fails correctly as `c` is not in `B`
const b_c: B = {
b: '',
c: ''
};
// Should fail because `c` is not in `B` while `b` is not in `AB`, but works instead
const acb_bc: AC|B = {
b: '',
c: ''
};
// Fails correctly as `x` in in neither `AC` nor `B`
const acb_bx: AC|B = {
b: '',
x: ''
};
// Fails correctly as `x` in in neither `AC` nor `B`
const acb_acx: AC|B = {
a: '',
c: '',
x: ''
};


//// [unionTypeExcessPropertyCheck.js]
// Fails correctly as `b` is not in `AC`
var ac_b = {
b: '',
c: ''
};
// Fails correctly as `c` is not in `B`
var b_c = {
b: '',
c: ''
};
// Should fail because `c` is not in `B` while `b` is not in `AB`, but works instead
var acb_bc = {
b: '',
c: ''
};
// Fails correctly as `x` in in neither `AC` nor `B`
var acb_bx = {
b: '',
x: ''
};
// Fails correctly as `x` in in neither `AC` nor `B`
var acb_acx = {
a: '',
c: '',
x: ''
};
88 changes: 88 additions & 0 deletions tests/baselines/reference/unionTypeExcessPropertyCheck.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//// [tests/cases/conformance/types/union/unionTypeExcessPropertyCheck.ts] ////

=== unionTypeExcessPropertyCheck.ts ===
type AC = {
>AC : Symbol(AC, Decl(unionTypeExcessPropertyCheck.ts, 0, 0))

a: string,
>a : Symbol(a, Decl(unionTypeExcessPropertyCheck.ts, 0, 11))

c: string
>c : Symbol(c, Decl(unionTypeExcessPropertyCheck.ts, 1, 14))

};
type B = {
>B : Symbol(B, Decl(unionTypeExcessPropertyCheck.ts, 3, 2))

b: string
>b : Symbol(b, Decl(unionTypeExcessPropertyCheck.ts, 4, 10))

};

// Fails correctly as `b` is not in `AC`
const ac_b: AC = {
>ac_b : Symbol(ac_b, Decl(unionTypeExcessPropertyCheck.ts, 9, 5))
>AC : Symbol(AC, Decl(unionTypeExcessPropertyCheck.ts, 0, 0))

b: '',
>b : Symbol(b, Decl(unionTypeExcessPropertyCheck.ts, 9, 18))

c: ''
>c : Symbol(c, Decl(unionTypeExcessPropertyCheck.ts, 10, 14))

};
// Fails correctly as `c` is not in `B`
const b_c: B = {
>b_c : Symbol(b_c, Decl(unionTypeExcessPropertyCheck.ts, 14, 5))
>B : Symbol(B, Decl(unionTypeExcessPropertyCheck.ts, 3, 2))

b: '',
>b : Symbol(b, Decl(unionTypeExcessPropertyCheck.ts, 14, 16))

c: ''
>c : Symbol(c, Decl(unionTypeExcessPropertyCheck.ts, 15, 14))

};
// Should fail because `c` is not in `B` while `b` is not in `AB`, but works instead
const acb_bc: AC|B = {
>acb_bc : Symbol(acb_bc, Decl(unionTypeExcessPropertyCheck.ts, 19, 5))
>AC : Symbol(AC, Decl(unionTypeExcessPropertyCheck.ts, 0, 0))
>B : Symbol(B, Decl(unionTypeExcessPropertyCheck.ts, 3, 2))

b: '',
>b : Symbol(b, Decl(unionTypeExcessPropertyCheck.ts, 19, 22))

c: ''
>c : Symbol(c, Decl(unionTypeExcessPropertyCheck.ts, 20, 14))

};
// Fails correctly as `x` in in neither `AC` nor `B`
const acb_bx: AC|B = {
>acb_bx : Symbol(acb_bx, Decl(unionTypeExcessPropertyCheck.ts, 24, 5))
>AC : Symbol(AC, Decl(unionTypeExcessPropertyCheck.ts, 0, 0))
>B : Symbol(B, Decl(unionTypeExcessPropertyCheck.ts, 3, 2))

b: '',
>b : Symbol(b, Decl(unionTypeExcessPropertyCheck.ts, 24, 22))

x: ''
>x : Symbol(x, Decl(unionTypeExcessPropertyCheck.ts, 25, 10))

};
// Fails correctly as `x` in in neither `AC` nor `B`
const acb_acx: AC|B = {
>acb_acx : Symbol(acb_acx, Decl(unionTypeExcessPropertyCheck.ts, 29, 5))
>AC : Symbol(AC, Decl(unionTypeExcessPropertyCheck.ts, 0, 0))
>B : Symbol(B, Decl(unionTypeExcessPropertyCheck.ts, 3, 2))

a: '',
>a : Symbol(a, Decl(unionTypeExcessPropertyCheck.ts, 29, 23))

c: '',
>c : Symbol(c, Decl(unionTypeExcessPropertyCheck.ts, 30, 10))

x: ''
>x : Symbol(x, Decl(unionTypeExcessPropertyCheck.ts, 31, 10))

};

Loading