mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Implement Symbol-based disposability checking
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
parent
6ff8065d1c
commit
dadd7418ed
@ -17546,10 +17546,47 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
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 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;
|
||||
@ -44997,21 +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) {
|
||||
const globalAsyncDisposableType = getGlobalAsyncDisposableType(/*reportErrors*/ true);
|
||||
const globalDisposableType = getGlobalDisposableType(/*reportErrors*/ true);
|
||||
if (globalAsyncDisposableType !== emptyObjectType && globalDisposableType !== emptyObjectType) {
|
||||
const optionalDisposableType = getUnionType([globalAsyncDisposableType, globalDisposableType, nullType, undefinedType]);
|
||||
checkTypeAssignableTo(widenTypeForVariableLikeDeclaration(initializerType, node), optionalDisposableType, 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) {
|
||||
const globalDisposableType = getGlobalDisposableType(/*reportErrors*/ true);
|
||||
if (globalDisposableType !== emptyObjectType) {
|
||||
const optionalDisposableType = getUnionType([globalDisposableType, nullType, undefinedType]);
|
||||
checkTypeAssignableTo(widenTypeForVariableLikeDeclaration(initializerType, node), optionalDisposableType, 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(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
// @target: esnext
|
||||
// @lib: esnext
|
||||
|
||||
// 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 {};
|
||||
Loading…
x
Reference in New Issue
Block a user