Skip to content

Skip ambient modules in globalThis - #48938

Merged
Nathan Shively-Sanders (sandersn) merged 2 commits into
mainfrom
skip-ambient-modules-in-globalThis
May 5, 2022
Merged

Nathan Shively-Sanders (sandersn) merged 2 commits into
mainfrom
skip-ambient-modules-in-globalThis

Conversation

@sandersn

Copy link
Copy Markdown
Member

Previously, globalThis mistakenly included ambient modules, even though these are not values:

declare module "ambientModule" {
  export type typ = 1
  export var val: typ
}
type Oops = (typeof globalThis)[\"ambientModule\"]

This PR adds ambient modules to the kinds of things that are skipped when constructing globalThis' properties, along with block-scoped variables.

Fixes #48147

Previously, globalThis mistakenly included ambient modules, even though
these are not values:

```ts
declare module "ambientModule" {
  export type typ = 1
  export var val: typ
}
type Oops = (typeof globalThis)[\"ambientModule\"]
```

This PR adds ambient modules to the kinds of things that are skipped
when constructing `globalThis`' properties, along with block-scoped
variables.
Comment thread src/compiler/checker.ts Outdated
const varsOnly = new Map<string, Symbol>() as SymbolTable;
members.forEach(p => {
if (!(p.flags & SymbolFlags.BlockScoped)) {
if (!(p.flags & SymbolFlags.BlockScoped) && !(p.flags & SymbolFlags.ValueModule && some(p.declarations, isAmbientModule))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some questions:

  • what's this ValueModule symbol flag/what is a value module?
  • is it precise enough to check if some declaration is an ambient module? what if some of the other declarations are not ambient modules? (that question may be answered by the one above)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • A value module is a namespace OR declare module "foo" with values inside it. A namespace module is a namespace that has only types inside it. (Both kinds of module can contain other namespaces; any nested value turns the parent namespaces into value modules.)
  • I think the binder only allows ambient modules to merge with ambient modules? I'll have to check.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, the excludes are the same as other value modules: Value & ~(Function | Class | RegularEnum | ValueModule)

I'll see if I can name a function, class or enum "foo", but I don't think I can.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, effectively only other ambient modules can merge with ambient modules.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if I can name a function, class or enum "foo", but I don't think I can.

You can if you use a js merge, eg,

globalThis['"module"'] = function () {}

or similar.


type GlobalThis = keyof typeof globalThis
// should error
const bad2: GlobalThis = 'ambientModule'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what's being tested here, because the use of quotes makes it confusing for me.
const bad2: GlobalThis = 'ambientModule' should error with or without this bug fix, no? Because the ambient module is "ambientModule", with the quotes. Or am I misunderstanding something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the original test case, which was already confusing. I should probably just leave it out: it turns out that keyof turns "\"foo\"" into just "foo". But it's not relevant to whether "\"foo\"" should be there in the first place.

Comment thread src/compiler/checker.ts Outdated
const varsOnly = new Map<string, Symbol>() as SymbolTable;
members.forEach(p => {
if (!(p.flags & SymbolFlags.BlockScoped)) {
if (!(p.flags & SymbolFlags.BlockScoped) && !(p.flags & SymbolFlags.ValueModule && some(p.declarations, isAmbientModule))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some? What if you make a global named "module" that ?merges? with the ambient module named "module"? (Is such a merge even possible?)

@sandersn Nathan Shively-Sanders (sandersn) May 4, 2022 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you make a global named "module"?

Edit: Ah, you give an example above. I guess every then?

The modules are required to have at least one declaration so that our
treatment of `globalThis` stays the same, and
`globalThis.globalThis.globalThis` remains legal.
@sandersn
Nathan Shively-Sanders (sandersn) deleted the skip-ambient-modules-in-globalThis branch May 5, 2022 16:33
@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Author: Team For Milestone Bug PRs that fix a bug with a specific milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ambient external module name incorrectly appears in keyof typeof globalThis

4 participants