Add support for type parameters in isKnownProperty and create test case

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-07-28 21:49:10 +00:00
parent 07bdeb7f86
commit 7adcf162bf
2 changed files with 60 additions and 29 deletions

View File

@ -22890,9 +22890,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray);
}
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) {
return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) {
return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny
}
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
if (
@ -34263,32 +34263,36 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* @param name a property name to search
* @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType
*/
function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean {
if (targetType.flags & TypeFlags.Object) {
// For backwards compatibility a symbol-named property is satisfied by a string index signature. This
// is incorrect and inconsistent with element access expressions, where it is an error, so eventually
// we should remove this exception.
if (
getPropertyOfObjectType(targetType, name) ||
getApplicableIndexInfoForName(targetType, name) ||
isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) ||
isComparingJsxAttributes && isHyphenatedJsxName(name)
) {
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
return true;
}
}
if (targetType.flags & TypeFlags.Substitution) {
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;
}
}
}
return false;
function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean {
if (targetType.flags & TypeFlags.Object) {
// For backwards compatibility a symbol-named property is satisfied by a string index signature. This
// is incorrect and inconsistent with element access expressions, where it is an error, so eventually
// we should remove this exception.
if (
getPropertyOfObjectType(targetType, name) ||
getApplicableIndexInfoForName(targetType, name) ||
isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) ||
isComparingJsxAttributes && isHyphenatedJsxName(name)
) {
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
return true;
}
}
if (targetType.flags & TypeFlags.Substitution) {
return isKnownProperty((targetType as SubstitutionType).baseType, name, isComparingJsxAttributes);
}
if (targetType.flags & TypeFlags.TypeParameter) {
const constraint = getConstraintOfTypeParameter(targetType as TypeParameter);
return constraint ? isKnownProperty(constraint, name, isComparingJsxAttributes) : false;
}
if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) {
for (const t of (targetType as UnionOrIntersectionType).types) {
if (isKnownProperty(t, name, isComparingJsxAttributes)) {
return true;
}
}
}
return false;
}
function isExcessPropertyCheckTarget(type: Type): boolean {

View File

@ -0,0 +1,27 @@
// Test case for destructuring assignment with generic constraints issue
type DataType = 'a' | 'b';
declare function foo<T extends { dataType: DataType }>(template: T): [T, any, any];
declare function bar<T extends { dataType: DataType }>(template: T): [T, any];
function testDestructuringBug() {
// These work fine (and should continue to work)
const [, ,] = foo({ dataType: 'a', day: 0 });
const [x, y, z] = foo({ dataType: 'a', day: 0 });
const [,] = bar({ dataType: 'a', day: 0 });
const [a, b] = bar({ dataType: 'a', day: 0 });
// These should work but currently don't (this is the bug)
const [, , t] = foo({ dataType: 'a', day: 0 }); // Should not error
const [, u] = bar({ dataType: 'a', day: 0 }); // Should not error
console.log(x, y, z, t, a, b, u);
}
// Test that direct calls work fine (they do)
function testDirectCalls() {
const result1 = foo({ dataType: 'a', day: 0 });
const result2 = bar({ dataType: 'a', day: 0 });
console.log(result1, result2);
}