Fix lint issues and finalize implementation

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-07-24 17:01:12 +00:00
parent dadd7418ed
commit 9aa3c8fa44
5 changed files with 254 additions and 52 deletions

View File

@@ -17542,51 +17542,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
}
function getGlobalDisposableType(reportErrors: boolean) {
function _getGlobalDisposableType(reportErrors: boolean) {
return (deferredGlobalDisposableType ||= getGlobalType("Disposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
function getGlobalAsyncDisposableType(reportErrors: boolean) {
return (deferredGlobalAsyncDisposableType ||= getGlobalType("AsyncDisposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
function checkTypeIsDisposable(type: Type): boolean {
if (type.flags & (TypeFlags.Null | TypeFlags.Undefined)) {
return true; // null and undefined are allowed
}
// Handle union types
if (type.flags & TypeFlags.Union) {
return every((type as UnionType).types, checkTypeIsDisposable);
}
const disposePropertyName = getPropertyNameForKnownSymbolName("dispose");
const disposeProperty = getPropertyOfType(type, disposePropertyName);
return !!disposeProperty && !(disposeProperty.flags & SymbolFlags.Optional);
}
function checkTypeIsAsyncDisposable(type: Type): boolean {
if (type.flags & (TypeFlags.Null | TypeFlags.Undefined)) {
return true; // null and undefined are allowed
}
// Handle union types
if (type.flags & TypeFlags.Union) {
return every((type as UnionType).types, checkTypeIsAsyncDisposable);
}
const asyncDisposePropertyName = getPropertyNameForKnownSymbolName("asyncDispose");
const asyncDisposeProperty = getPropertyOfType(type, asyncDisposePropertyName);
if (asyncDisposeProperty && !(asyncDisposeProperty.flags & SymbolFlags.Optional)) {
return true;
}
// For await using, also check for Symbol.dispose as a fallback
return checkTypeIsDisposable(type);
}
function _getGlobalAsyncDisposableType(reportErrors: boolean) {
return (deferredGlobalAsyncDisposableType ||= getGlobalType("AsyncDisposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
function checkTypeIsDisposable(type: Type): boolean {
if (type.flags & (TypeFlags.Null | TypeFlags.Undefined)) {
return true; // null and undefined are allowed
}
// Handle union types
if (type.flags & TypeFlags.Union) {
return every((type as UnionType).types, checkTypeIsDisposable);
}
const disposePropertyName = getPropertyNameForKnownSymbolName("dispose");
const disposeProperty = getPropertyOfType(type, disposePropertyName);
return !!disposeProperty && !(disposeProperty.flags & SymbolFlags.Optional);
}
function checkTypeIsAsyncDisposable(type: Type): boolean {
if (type.flags & (TypeFlags.Null | TypeFlags.Undefined)) {
return true; // null and undefined are allowed
}
// Handle union types
if (type.flags & TypeFlags.Union) {
return every((type as UnionType).types, checkTypeIsAsyncDisposable);
}
const asyncDisposePropertyName = getPropertyNameForKnownSymbolName("asyncDispose");
const asyncDisposeProperty = getPropertyOfType(type, asyncDisposePropertyName);
if (asyncDisposeProperty && !(asyncDisposeProperty.flags & SymbolFlags.Optional)) {
return true;
}
// For await using, also check for Symbol.dispose as a fallback
return checkTypeIsDisposable(type);
}
function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined {
const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined);
return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType;
@@ -45034,16 +45034,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) {
const initializerType = checkExpressionCached(initializer);
checkTypeAssignableToAndOptionallyElaborate(initializerType, type, node, initializer, /*headMessage*/ undefined);
const blockScopeKind = getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped;
if (blockScopeKind === NodeFlags.AwaitUsing) {
if (!checkTypeIsAsyncDisposable(widenTypeForVariableLikeDeclaration(initializerType, node))) {
error(initializer, Diagnostics.The_initializer_of_an_await_using_declaration_must_be_either_an_object_with_a_Symbol_asyncDispose_or_Symbol_dispose_method_or_be_null_or_undefined);
}
}
else if (blockScopeKind === NodeFlags.Using) {
if (!checkTypeIsDisposable(widenTypeForVariableLikeDeclaration(initializerType, node))) {
error(initializer, Diagnostics.The_initializer_of_a_using_declaration_must_be_either_an_object_with_a_Symbol_dispose_method_or_be_null_or_undefined);
}
const blockScopeKind = getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped;
if (blockScopeKind === NodeFlags.AwaitUsing) {
if (!checkTypeIsAsyncDisposable(initializerType)) {
error(initializer, Diagnostics.The_initializer_of_an_await_using_declaration_must_be_either_an_object_with_a_Symbol_asyncDispose_or_Symbol_dispose_method_or_be_null_or_undefined);
}
}
else if (blockScopeKind === NodeFlags.Using) {
if (!checkTypeIsDisposable(initializerType)) {
error(initializer, Diagnostics.The_initializer_of_a_using_declaration_must_be_either_an_object_with_a_Symbol_dispose_method_or_be_null_or_undefined);
}
}
}
}

View File

@@ -0,0 +1,34 @@
usingDeclarationWithGlobalInterfaceModification.ts(26,17): error TS2850: The initializer of a 'using' declaration must be either an object with a '[Symbol.dispose]()' method, or be 'null' or 'undefined'.
==== usingDeclarationWithGlobalInterfaceModification.ts (1 errors) ====
// Test case that demonstrates the issue from https://github.com/microsoft/TypeScript/issues/62121
// When an empty global Disposable interface is declared, it should NOT affect
// the checking for Symbol.dispose properties
declare global {
interface Disposable {}
}
// This should pass - has Symbol.dispose method
const validDisposable = {
[Symbol.dispose]() {
// disposed
}
};
// This should fail - no Symbol.dispose method
const invalidDisposable = {
cleanup() {
// cleanup
}
};
// With the fix, the checker should directly check for Symbol.dispose properties
// rather than relying on assignability to the global Disposable interface
using valid = validDisposable; // should pass
using invalid = invalidDisposable; // should error
~~~~~~~~~~~~~~~~~
!!! error TS2850: The initializer of a 'using' declaration must be either an object with a '[Symbol.dispose]()' method, or be 'null' or 'undefined'.
export {};

View File

@@ -0,0 +1,53 @@
//// [tests/cases/compiler/usingDeclarationWithGlobalInterfaceModification.ts] ////
//// [usingDeclarationWithGlobalInterfaceModification.ts]
// Test case that demonstrates the issue from https://github.com/microsoft/TypeScript/issues/62121
// When an empty global Disposable interface is declared, it should NOT affect
// the checking for Symbol.dispose properties
declare global {
interface Disposable {}
}
// This should pass - has Symbol.dispose method
const validDisposable = {
[Symbol.dispose]() {
// disposed
}
};
// This should fail - no Symbol.dispose method
const invalidDisposable = {
cleanup() {
// cleanup
}
};
// With the fix, the checker should directly check for Symbol.dispose properties
// rather than relying on assignability to the global Disposable interface
using valid = validDisposable; // should pass
using invalid = invalidDisposable; // should error
export {};
//// [usingDeclarationWithGlobalInterfaceModification.js]
// Test case that demonstrates the issue from https://github.com/microsoft/TypeScript/issues/62121
// When an empty global Disposable interface is declared, it should NOT affect
// the checking for Symbol.dispose properties
// This should pass - has Symbol.dispose method
const validDisposable = {
[Symbol.dispose]() {
// disposed
}
};
// This should fail - no Symbol.dispose method
const invalidDisposable = {
cleanup() {
// cleanup
}
};
// With the fix, the checker should directly check for Symbol.dispose properties
// rather than relying on assignability to the global Disposable interface
using valid = validDisposable; // should pass
using invalid = invalidDisposable; // should error
export {};

View File

@@ -0,0 +1,50 @@
//// [tests/cases/compiler/usingDeclarationWithGlobalInterfaceModification.ts] ////
=== usingDeclarationWithGlobalInterfaceModification.ts ===
// Test case that demonstrates the issue from https://github.com/microsoft/TypeScript/issues/62121
// When an empty global Disposable interface is declared, it should NOT affect
// the checking for Symbol.dispose properties
declare global {
>global : Symbol(global, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 0, 0))
interface Disposable {}
>Disposable : Symbol(Disposable, Decl(lib.esnext.disposable.d.ts, --, --), Decl(usingDeclarationWithGlobalInterfaceModification.ts, 4, 16))
}
// This should pass - has Symbol.dispose method
const validDisposable = {
>validDisposable : Symbol(validDisposable, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 9, 5))
[Symbol.dispose]() {
>[Symbol.dispose] : Symbol([Symbol.dispose], Decl(usingDeclarationWithGlobalInterfaceModification.ts, 9, 25))
>Symbol.dispose : Symbol(SymbolConstructor.dispose, Decl(lib.esnext.disposable.d.ts, --, --))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))
>dispose : Symbol(SymbolConstructor.dispose, Decl(lib.esnext.disposable.d.ts, --, --))
// disposed
}
};
// This should fail - no Symbol.dispose method
const invalidDisposable = {
>invalidDisposable : Symbol(invalidDisposable, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 16, 5))
cleanup() {
>cleanup : Symbol(cleanup, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 16, 27))
// cleanup
}
};
// With the fix, the checker should directly check for Symbol.dispose properties
// rather than relying on assignability to the global Disposable interface
using valid = validDisposable; // should pass
>valid : Symbol(valid, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 24, 5))
>validDisposable : Symbol(validDisposable, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 9, 5))
using invalid = invalidDisposable; // should error
>invalid : Symbol(invalid, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 25, 5))
>invalidDisposable : Symbol(invalidDisposable, Decl(usingDeclarationWithGlobalInterfaceModification.ts, 16, 5))
export {};

View File

@@ -0,0 +1,65 @@
//// [tests/cases/compiler/usingDeclarationWithGlobalInterfaceModification.ts] ////
=== usingDeclarationWithGlobalInterfaceModification.ts ===
// Test case that demonstrates the issue from https://github.com/microsoft/TypeScript/issues/62121
// When an empty global Disposable interface is declared, it should NOT affect
// the checking for Symbol.dispose properties
declare global {
>global : any
> : ^^^
interface Disposable {}
}
// This should pass - has Symbol.dispose method
const validDisposable = {
>validDisposable : { [Symbol.dispose](): void; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>{ [Symbol.dispose]() { // disposed }} : { [Symbol.dispose](): void; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Symbol.dispose]() {
>[Symbol.dispose] : () => void
> : ^^^^^^^^^^
>Symbol.dispose : unique symbol
> : ^^^^^^^^^^^^^
>Symbol : SymbolConstructor
> : ^^^^^^^^^^^^^^^^^
>dispose : unique symbol
> : ^^^^^^^^^^^^^
// disposed
}
};
// This should fail - no Symbol.dispose method
const invalidDisposable = {
>invalidDisposable : { cleanup(): void; }
> : ^^^^^^^^^^^^^^^^^^^^
>{ cleanup() { // cleanup }} : { cleanup(): void; }
> : ^^^^^^^^^^^^^^^^^^^^
cleanup() {
>cleanup : () => void
> : ^^^^^^^^^^
// cleanup
}
};
// With the fix, the checker should directly check for Symbol.dispose properties
// rather than relying on assignability to the global Disposable interface
using valid = validDisposable; // should pass
>valid : { [Symbol.dispose](): void; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>validDisposable : { [Symbol.dispose](): void; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
using invalid = invalidDisposable; // should error
>invalid : { cleanup(): void; }
> : ^^^^^^^^^^^^^^^^^^^^
>invalidDisposable : { cleanup(): void; }
> : ^^^^^^^^^^^^^^^^^^^^
export {};