From 7adcf162bf8990d24a2397c4d965e4c46075c109 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:49:10 +0000 Subject: [PATCH] Add support for type parameters in isKnownProperty and create test case Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- src/compiler/checker.ts | 62 ++++++++++--------- .../destructuringAssignmentWithConstraints.ts | 27 ++++++++ 2 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 tests/cases/compiler/destructuringAssignmentWithConstraints.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 77f35376da7..cf55019b980 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 { diff --git a/tests/cases/compiler/destructuringAssignmentWithConstraints.ts b/tests/cases/compiler/destructuringAssignmentWithConstraints.ts new file mode 100644 index 00000000000..45af10ea695 --- /dev/null +++ b/tests/cases/compiler/destructuringAssignmentWithConstraints.ts @@ -0,0 +1,27 @@ +// Test case for destructuring assignment with generic constraints issue + +type DataType = 'a' | 'b'; + +declare function foo(template: T): [T, any, any]; +declare function bar(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); +} \ No newline at end of file