Skip to content

Derive disposability checks from known symbols rather than global interfaces #62122

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jul 24, 2025

This PR addresses an issue where TypeScript's disposability checking for using and await using declarations was vulnerable to user modifications of global Disposable and AsyncDisposable interfaces.

Problem

Previously, the checker determined disposability by testing assignability against global Disposable/AsyncDisposable interfaces. This approach had a significant weakness: any script that declared an empty global interface could unintentionally modify the checker's behavior:

declare global {
  interface Disposable {}
}

// With the empty global interface, this would incorrectly pass
using invalid = { cleanup() {} }; // No Symbol.dispose method!

Solution

This PR changes the disposability checking to directly examine Symbol.dispose and Symbol.asyncDispose properties, following the same pattern used for iteration checks with Symbol.iterator.

The key changes:

  1. Added new helper functions: checkTypeIsDisposable() and checkTypeIsAsyncDisposable() that use getPropertyNameForKnownSymbolName() to check for the actual symbol properties
  2. Updated variable declaration validation: Modified the using and await using declaration checking to use these symbol-based checks instead of global interface assignability
  3. Proper edge case handling: Correctly handles union types, null/undefined values, and preserves all existing behavior

Example

With this fix, disposability is determined by the presence of the actual symbol methods:

declare global {
  interface Disposable {} // Empty interface no longer affects checking
}

const valid = {
  [Symbol.dispose]() {} // Has the symbol method - passes
};

const invalid = {
  cleanup() {} // No symbol method - correctly fails
};

using a = valid;   // ✅ Works
using b = invalid; // ❌ Error: must have [Symbol.dispose]() method

Testing

  • Added comprehensive test case demonstrating the fix works even with empty global interfaces
  • All existing using declaration tests continue to pass
  • Verified null/undefined handling works correctly
  • Confirmed both sync and async disposability checking work as expected

Fixes #62121.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Jul 24, 2025
@Copilot Copilot AI changed the title [WIP] Suggestion: Derive disposability checks from known symbols rather than global interfaces Derive disposability checks from known symbols rather than global interfaces Jul 24, 2025
@Copilot Copilot AI requested a review from RyanCavanaugh July 24, 2025 17:03
Copilot finished work on behalf of RyanCavanaugh July 24, 2025 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Suggestion: Derive disposability checks from known symbols rather than global interfaces
3 participants