From ad54bfd496cd7963de792b9afa4f3a4cff99fb08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:44:54 +0000 Subject: [PATCH 1/2] Initial plan From a3b309dadf3948027ed4c350707a5b6445009604 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:53:09 +0000 Subject: [PATCH 2/2] Add comprehensive documentation on TypeScript binder and symbols system Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .github/copilot-library.md | 338 ++++++++++++++---- .github/copilot-questions.md | 9 +- .../symbolsDocumentationTest.errors.txt | 48 +++ .../reference/symbolsDocumentationTest.js | 77 ++++ .../symbolsDocumentationTest.symbols | 98 +++++ .../reference/symbolsDocumentationTest.types | 139 +++++++ .../compiler/symbolsDocumentationTest.ts | 42 +++ 7 files changed, 676 insertions(+), 75 deletions(-) create mode 100644 tests/baselines/reference/symbolsDocumentationTest.errors.txt create mode 100644 tests/baselines/reference/symbolsDocumentationTest.js create mode 100644 tests/baselines/reference/symbolsDocumentationTest.symbols create mode 100644 tests/baselines/reference/symbolsDocumentationTest.types create mode 100644 tests/cases/compiler/symbolsDocumentationTest.ts diff --git a/.github/copilot-library.md b/.github/copilot-library.md index 971877ca9afb7..33082cfce3771 100644 --- a/.github/copilot-library.md +++ b/.github/copilot-library.md @@ -91,75 +91,271 @@ new Base(); // Should error - cannot instantiate abstract class ``` -# Fourslash Testing - -> Fourslash is our testing system for language service functionality - -Fourslash tests are interactive TypeScript language service tests. They validate IDE features like completions, quick info, navigation, and refactoring. You create a new fourslash test by putting a file in `tests/cases/fourslash`. - -They have a "user code" section, prefixed by four slashes per line, followed by one or more instructions for what to do with the code. Within the code, a `/**/` comment creates an anonymous "marker"; named markers use alphanumeric text between the stars (`/*here*/`). You can use the markers to refer to specific positions in the code: -```typescript -////function foo(x: number) { -//// return x + 1; -////} -////let result = foo(/**/42); - -goTo.marker(); -verify.baselineSignatureHelp(); -``` - -Use `// @Filename:` to define multiple files: -```typescript -// @Filename: /a.ts -////export const value = 42; - -// @Filename: /b.ts -////import { value } from './a'; -////console.log(/*marker*/value); -``` - -Use `[|text|]` to define text ranges, which can be used for selecting text or describing expected Find All References results. -```typescript -////function test() { -//// [|return 42;|] -////} -``` - -More code examples: -```typescript -// Moving the virtual caret around -goTo.marker("markerName"); // Navigate to marker -goTo.marker(); // Navigate to anonymous marker /**/ - -// Verifying expected results (generally preferred over baselines in these tests) -verify.currentLineContentIs("expected content"); -verify.completions({ includes: "itemName" }); -verify.completions({ excludes: "itemName" }); -verify.quickInfoIs("expected info"); -verify.codeFix({ - description: "Fix description", - newFileContent: "expected content after fix" -}); - -// Completions testing -verify.completions({ - marker: "1", - includes: { name: "foo", source: "/a", hasAction: true }, - isNewIdentifierLocation: true, - preferences: { includeCompletionsForModuleExports: true } -}); - -// Code fixes testing -verify.codeFix({ - description: "Add missing property", - index: 0, - newFileContent: `class C { - property: string; - method() { this.property = "value"; } -}` -}); - -// Formatting -format.document(); -verify.currentLineContentIs("formatted content"); +# TypeScript Binder and Symbols System + +> Technical overview of how symbols in TypeScript work + +TypeScript's binder creates symbols that represent declarations in your code, and the checker uses these symbols for type checking and name resolution. Understanding this system is crucial for debugging complex issues and understanding how TypeScript resolves names and types. + +## What are Symbols? + +Symbols are TypeScript's internal representation of declarations. Each symbol has: + +- **flags**: A bitmask from `SymbolFlags` enum that describes what kind of declaration it represents +- **escapedName**: The internal string representation of the symbol's name +- **declarations**: Array of AST nodes that declare this symbol +- **members**: SymbolTable for nested symbols (class members, interface properties, etc.) +- **exports**: SymbolTable for module exports +- Internal tracking fields for type checking and resolution + +```ts +// From types.ts +export interface Symbol { + flags: SymbolFlags; // Symbol flags + escapedName: __String; // Name of symbol + declarations?: Declaration[]; // Declarations associated with this symbol + valueDeclaration?: Declaration; // First value declaration of the symbol + members?: SymbolTable; // Class, interface or object literal instance members + exports?: SymbolTable; // Module exports + // ... additional internal fields +} +``` + +## SymbolFlags + +SymbolFlags is a bitmask enum that categorizes what kinds of declarations a symbol represents: + +```ts +export const enum SymbolFlags { + FunctionScopedVariable = 1 << 0, // var or parameter + BlockScopedVariable = 1 << 1, // let or const + Property = 1 << 2, // Property or enum member + Function = 1 << 4, // Function + Class = 1 << 5, // Class + Interface = 1 << 6, // Interface + TypeAlias = 1 << 19, // Type alias + // ... many more + + // Composite flags for common combinations + Variable = FunctionScopedVariable | BlockScopedVariable, + Value = Variable | Property | Function | Class | /* ... */, + Type = Class | Interface | TypeAlias | /* ... */, +} +``` + +Key composite flags: +- **Value**: Symbols that exist at runtime (variables, functions, classes, etc.) +- **Type**: Symbols that exist only at compile time (interfaces, type aliases, etc.) +- **Namespace**: Symbols that can contain other symbols (modules, enums, etc.) + +## SymbolTable + +A SymbolTable is simply a `Map<__String, Symbol>` that stores symbols by their escaped names: + +```ts +export type SymbolTable = Map<__String, Symbol>; + +export function createSymbolTable(symbols?: readonly Symbol[]): SymbolTable { + const result = new Map<__String, Symbol>(); + if (symbols) { + for (const symbol of symbols) { + result.set(symbol.escapedName, symbol); + } + } + return result; +} +``` + +## How the Binder Works + +The binder (binder.ts) traverses the AST and creates symbols for declarations. Key functions: + +### createSymbol +Creates a new symbol with specified flags and name: +```ts +function createSymbol(flags: SymbolFlags, name: __String): Symbol { + symbolCount++; + return new Symbol(flags, name); +} +``` + +### declareSymbol +Adds symbols to symbol tables, handling conflicts and merging: +```ts +function declareSymbol( + symbolTable: SymbolTable, + parent: Symbol | undefined, + node: Declaration, + includes: SymbolFlags, + excludes: SymbolFlags +): Symbol +``` + +The `excludes` parameter defines what kinds of symbols cannot coexist. For example: +- `BlockScopedVariable` excludes all `Value` symbols (no redeclaration) +- `FunctionScopedVariable` excludes `Value & ~FunctionScopedVariable` (can merge with other vars) +- `Interface` excludes `Type & ~(Interface | Class)` (can merge with other interfaces and classes) + +### Symbol Resolution Process + +During binding, each declaration gets processed: +1. Determine the symbol flags based on declaration type +2. Get or create symbol in appropriate symbol table +3. Check for conflicts using excludes flags +4. Add declaration to symbol's declarations array +5. Set up members/exports SymbolTables if needed + +## How Name Resolution Works + +The `resolveName` function (created by `createNameResolver` in utilities.ts) implements lexical scoping by walking up the scope chain: + +```ts +function resolveNameHelper( + location: Node | undefined, + name: __String, + meaning: SymbolFlags, // What kind of symbol we're looking for + // ... +): Symbol | undefined +``` + +### Resolution Algorithm + +1. **Local Scope Check**: If current node can have locals, check its symbol table +2. **Scope-Specific Rules**: Apply visibility rules based on context: + - Function parameters only visible in function body + - Type parameters visible in parameter list and return type + - Block-scoped variables respect block boundaries +3. **Parent Scope**: Move up to parent node and repeat +4. **Module Exports**: Check module exports if in module context +5. **Global Scope**: Finally check global symbols + +### Context-Sensitive Resolution + +The `meaning` parameter filters which symbols are considered: +```ts +// Looking for a type +resolveName(location, "x", SymbolFlags.Type, ...) +// Looking for a value +resolveName(location, "x", SymbolFlags.Value, ...) +``` + +## The Classic Example Explained + +```ts +type x = number; // Creates symbol: flags=TypeAlias, name="x" +function fn(x: string) { // Creates symbol: flags=FunctionScopedVariable, name="x" + let y: x = x; // Two different lookups happen here +} +``` + +When the checker processes `let y: x = x;`: + +1. **Type position `x`**: + - Calls `resolveName(location, "x", SymbolFlags.Type, ...)` + - Walks up scopes looking for Type symbols + - Finds the type alias `x = number` in global scope + - Returns that symbol + +2. **Value position `x`**: + - Calls `resolveName(location, "x", SymbolFlags.Value, ...)` + - Checks function locals first + - Finds parameter `x: string` + - Returns that symbol + +This demonstrates how: +- The same name can resolve to different symbols +- Context (Type vs Value) affects resolution +- Scope hierarchy determines which symbol is found +- The binder creates appropriate symbol tables for different scopes + +## Symbol Merging + +Some declarations can merge their symbols: +- Multiple `var` declarations with same name +- `interface` declarations merge their members +- `namespace` and `enum` can merge with compatible declarations +- Classes and interfaces can merge (declaration merging) + +The binder handles this by checking `excludes` flags and either merging with existing symbols or creating conflicts. + +## Debugging Tips + +When debugging symbol-related issues: +1. Check what SymbolFlags a symbol has using `symbol.flags & SymbolFlags.SomeFlag` +2. Print symbol names with `symbolToString()` or `symbol.escapedName` +3. Examine symbol.declarations to see all AST nodes for that symbol +4. Use checker's `getSymbolAtLocation()` to see what symbol a node resolves to +5. Check if you're looking for the right meaning (Type vs Value vs Namespace) + +# Fourslash Testing + +> Fourslash is our testing system for language service functionality + +Fourslash tests are interactive TypeScript language service tests. They validate IDE features like completions, quick info, navigation, and refactoring. You create a new fourslash test by putting a file in `tests/cases/fourslash`. + +They have a "user code" section, prefixed by four slashes per line, followed by one or more instructions for what to do with the code. Within the code, a `/**/` comment creates an anonymous "marker"; named markers use alphanumeric text between the stars (`/*here*/`). You can use the markers to refer to specific positions in the code: +```typescript +////function foo(x: number) { +//// return x + 1; +////} +////let result = foo(/**/42); + +goTo.marker(); +verify.baselineSignatureHelp(); +``` + +Use `// @Filename:` to define multiple files: +```typescript +// @Filename: /a.ts +////export const value = 42; + +// @Filename: /b.ts +////import { value } from './a'; +////console.log(/*marker*/value); +``` + +Use `[|text|]` to define text ranges, which can be used for selecting text or describing expected Find All References results. +```typescript +////function test() { +//// [|return 42;|] +////} +``` + +More code examples: +```typescript +// Moving the virtual caret around +goTo.marker("markerName"); // Navigate to marker +goTo.marker(); // Navigate to anonymous marker /**/ + +// Verifying expected results (generally preferred over baselines in these tests) +verify.currentLineContentIs("expected content"); +verify.completions({ includes: "itemName" }); +verify.completions({ excludes: "itemName" }); +verify.quickInfoIs("expected info"); +verify.codeFix({ + description: "Fix description", + newFileContent: "expected content after fix" +}); + +// Completions testing +verify.completions({ + marker: "1", + includes: { name: "foo", source: "/a", hasAction: true }, + isNewIdentifierLocation: true, + preferences: { includeCompletionsForModuleExports: true } +}); + +// Code fixes testing +verify.codeFix({ + description: "Add missing property", + index: 0, + newFileContent: `class C { + property: string; + method() { this.property = "value"; } +}` +}); + +// Formatting +format.document(); +verify.currentLineContentIs("formatted content"); ``` diff --git a/.github/copilot-questions.md b/.github/copilot-questions.md index 250d4bf2d776d..b70074ee4f0a7 100644 --- a/.github/copilot-questions.md +++ b/.github/copilot-questions.md @@ -1,4 +1,5 @@ -Questions I have that I think the developers of this project can help me with: - * How does control flow analysis represent a circular graph? I checked the documentation server for "cfa" and "control flow" - * How do I tell if a symbol is in the global scope? I checked the documentation server for topics referencing "symbol" and "global" - * What is an `EscapedName`, exactly? +Questions I have that I think the developers of this project can help me with: + * How does control flow analysis represent a circular graph? I checked the documentation server for "cfa" and "control flow" + * How do I tell if a symbol is in the global scope? I checked the documentation server for topics referencing "symbol" and "global" + * What is an `EscapedName`, exactly? + * How does the binder handle complex symbol merging scenarios like when a namespace merges with a class that merges with an interface? I searched for "symbol merging", "declaration merging", and "namespace class interface merge" but want to understand the exact algorithm. diff --git a/tests/baselines/reference/symbolsDocumentationTest.errors.txt b/tests/baselines/reference/symbolsDocumentationTest.errors.txt new file mode 100644 index 0000000000000..49136977bad89 --- /dev/null +++ b/tests/baselines/reference/symbolsDocumentationTest.errors.txt @@ -0,0 +1,48 @@ +symbolsDocumentationTest.ts(7,7): error TS2322: Type 'string' is not assignable to type 'number'. + + +==== symbolsDocumentationTest.ts (1 errors) ==== + // Test case for symbols documentation - demonstrates name resolution behavior + + type x = number; // Type symbol in global scope + const globalVar = 42; // Value symbol in global scope + + function fn(x: string, globalVar: boolean) { // Parameters shadow global symbols + let y: x = x; // Type 'x' resolves to global type alias, value 'x' resolves to parameter + ~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. + let z = globalVar; // Resolves to parameter, not global variable + + function nested() { + let x = "nested"; // Block-scoped variable shadows parameter + return x.length; // Resolves to local variable + } + + return nested(); + } + + interface I { // Interface symbol + method(): void; + } + + interface I { // Interface merging - adds to same symbol + prop: string; + } + + class C implements I { // Class symbol, can merge with interface + prop = "test"; + method() {} + } + + namespace N { // Namespace symbol + export const value = 1; + } + + namespace N { // Namespace merging + export function func() {} + } + + // Test various contexts where name resolution differs + const test1: I = new C(); // 'I' resolves to merged interface, 'C' resolves to class + const test2 = N.value; // 'N' resolves to merged namespace + const test3 = N.func(); // Same namespace, different export \ No newline at end of file diff --git a/tests/baselines/reference/symbolsDocumentationTest.js b/tests/baselines/reference/symbolsDocumentationTest.js new file mode 100644 index 0000000000000..cce556d9b17ec --- /dev/null +++ b/tests/baselines/reference/symbolsDocumentationTest.js @@ -0,0 +1,77 @@ +//// [tests/cases/compiler/symbolsDocumentationTest.ts] //// + +//// [symbolsDocumentationTest.ts] +// Test case for symbols documentation - demonstrates name resolution behavior + +type x = number; // Type symbol in global scope +const globalVar = 42; // Value symbol in global scope + +function fn(x: string, globalVar: boolean) { // Parameters shadow global symbols + let y: x = x; // Type 'x' resolves to global type alias, value 'x' resolves to parameter + let z = globalVar; // Resolves to parameter, not global variable + + function nested() { + let x = "nested"; // Block-scoped variable shadows parameter + return x.length; // Resolves to local variable + } + + return nested(); +} + +interface I { // Interface symbol + method(): void; +} + +interface I { // Interface merging - adds to same symbol + prop: string; +} + +class C implements I { // Class symbol, can merge with interface + prop = "test"; + method() {} +} + +namespace N { // Namespace symbol + export const value = 1; +} + +namespace N { // Namespace merging + export function func() {} +} + +// Test various contexts where name resolution differs +const test1: I = new C(); // 'I' resolves to merged interface, 'C' resolves to class +const test2 = N.value; // 'N' resolves to merged namespace +const test3 = N.func(); // Same namespace, different export + +//// [symbolsDocumentationTest.js] +// Test case for symbols documentation - demonstrates name resolution behavior +var globalVar = 42; // Value symbol in global scope +function fn(x, globalVar) { + var y = x; // Type 'x' resolves to global type alias, value 'x' resolves to parameter + var z = globalVar; // Resolves to parameter, not global variable + function nested() { + var x = "nested"; // Block-scoped variable shadows parameter + return x.length; // Resolves to local variable + } + return nested(); +} +var C = /** @class */ (function () { + function C() { + this.prop = "test"; + } + C.prototype.method = function () { }; + return C; +}()); +var N; +(function (N) { + N.value = 1; +})(N || (N = {})); +(function (N) { + function func() { } + N.func = func; +})(N || (N = {})); +// Test various contexts where name resolution differs +var test1 = new C(); // 'I' resolves to merged interface, 'C' resolves to class +var test2 = N.value; // 'N' resolves to merged namespace +var test3 = N.func(); // Same namespace, different export diff --git a/tests/baselines/reference/symbolsDocumentationTest.symbols b/tests/baselines/reference/symbolsDocumentationTest.symbols new file mode 100644 index 0000000000000..878e2b5b0c843 --- /dev/null +++ b/tests/baselines/reference/symbolsDocumentationTest.symbols @@ -0,0 +1,98 @@ +//// [tests/cases/compiler/symbolsDocumentationTest.ts] //// + +=== symbolsDocumentationTest.ts === +// Test case for symbols documentation - demonstrates name resolution behavior + +type x = number; // Type symbol in global scope +>x : Symbol(x, Decl(symbolsDocumentationTest.ts, 0, 0)) + +const globalVar = 42; // Value symbol in global scope +>globalVar : Symbol(globalVar, Decl(symbolsDocumentationTest.ts, 3, 5)) + +function fn(x: string, globalVar: boolean) { // Parameters shadow global symbols +>fn : Symbol(fn, Decl(symbolsDocumentationTest.ts, 3, 21)) +>x : Symbol(x, Decl(symbolsDocumentationTest.ts, 5, 12)) +>globalVar : Symbol(globalVar, Decl(symbolsDocumentationTest.ts, 5, 22)) + + let y: x = x; // Type 'x' resolves to global type alias, value 'x' resolves to parameter +>y : Symbol(y, Decl(symbolsDocumentationTest.ts, 6, 5)) +>x : Symbol(x, Decl(symbolsDocumentationTest.ts, 0, 0)) +>x : Symbol(x, Decl(symbolsDocumentationTest.ts, 5, 12)) + + let z = globalVar; // Resolves to parameter, not global variable +>z : Symbol(z, Decl(symbolsDocumentationTest.ts, 7, 5)) +>globalVar : Symbol(globalVar, Decl(symbolsDocumentationTest.ts, 5, 22)) + + function nested() { +>nested : Symbol(nested, Decl(symbolsDocumentationTest.ts, 7, 20)) + + let x = "nested"; // Block-scoped variable shadows parameter +>x : Symbol(x, Decl(symbolsDocumentationTest.ts, 10, 7)) + + return x.length; // Resolves to local variable +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(symbolsDocumentationTest.ts, 10, 7)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + } + + return nested(); +>nested : Symbol(nested, Decl(symbolsDocumentationTest.ts, 7, 20)) +} + +interface I { // Interface symbol +>I : Symbol(I, Decl(symbolsDocumentationTest.ts, 15, 1), Decl(symbolsDocumentationTest.ts, 19, 1)) + + method(): void; +>method : Symbol(I.method, Decl(symbolsDocumentationTest.ts, 17, 13)) +} + +interface I { // Interface merging - adds to same symbol +>I : Symbol(I, Decl(symbolsDocumentationTest.ts, 15, 1), Decl(symbolsDocumentationTest.ts, 19, 1)) + + prop: string; +>prop : Symbol(I.prop, Decl(symbolsDocumentationTest.ts, 21, 13)) +} + +class C implements I { // Class symbol, can merge with interface +>C : Symbol(C, Decl(symbolsDocumentationTest.ts, 23, 1)) +>I : Symbol(I, Decl(symbolsDocumentationTest.ts, 15, 1), Decl(symbolsDocumentationTest.ts, 19, 1)) + + prop = "test"; +>prop : Symbol(C.prop, Decl(symbolsDocumentationTest.ts, 25, 22)) + + method() {} +>method : Symbol(C.method, Decl(symbolsDocumentationTest.ts, 26, 16)) +} + +namespace N { // Namespace symbol +>N : Symbol(N, Decl(symbolsDocumentationTest.ts, 28, 1), Decl(symbolsDocumentationTest.ts, 32, 1)) + + export const value = 1; +>value : Symbol(value, Decl(symbolsDocumentationTest.ts, 31, 14)) +} + +namespace N { // Namespace merging +>N : Symbol(N, Decl(symbolsDocumentationTest.ts, 28, 1), Decl(symbolsDocumentationTest.ts, 32, 1)) + + export function func() {} +>func : Symbol(func, Decl(symbolsDocumentationTest.ts, 34, 13)) +} + +// Test various contexts where name resolution differs +const test1: I = new C(); // 'I' resolves to merged interface, 'C' resolves to class +>test1 : Symbol(test1, Decl(symbolsDocumentationTest.ts, 39, 5)) +>I : Symbol(I, Decl(symbolsDocumentationTest.ts, 15, 1), Decl(symbolsDocumentationTest.ts, 19, 1)) +>C : Symbol(C, Decl(symbolsDocumentationTest.ts, 23, 1)) + +const test2 = N.value; // 'N' resolves to merged namespace +>test2 : Symbol(test2, Decl(symbolsDocumentationTest.ts, 40, 5)) +>N.value : Symbol(N.value, Decl(symbolsDocumentationTest.ts, 31, 14)) +>N : Symbol(N, Decl(symbolsDocumentationTest.ts, 28, 1), Decl(symbolsDocumentationTest.ts, 32, 1)) +>value : Symbol(N.value, Decl(symbolsDocumentationTest.ts, 31, 14)) + +const test3 = N.func(); // Same namespace, different export +>test3 : Symbol(test3, Decl(symbolsDocumentationTest.ts, 41, 5)) +>N.func : Symbol(N.func, Decl(symbolsDocumentationTest.ts, 34, 13)) +>N : Symbol(N, Decl(symbolsDocumentationTest.ts, 28, 1), Decl(symbolsDocumentationTest.ts, 32, 1)) +>func : Symbol(N.func, Decl(symbolsDocumentationTest.ts, 34, 13)) + diff --git a/tests/baselines/reference/symbolsDocumentationTest.types b/tests/baselines/reference/symbolsDocumentationTest.types new file mode 100644 index 0000000000000..38c5a7d8c2728 --- /dev/null +++ b/tests/baselines/reference/symbolsDocumentationTest.types @@ -0,0 +1,139 @@ +//// [tests/cases/compiler/symbolsDocumentationTest.ts] //// + +=== symbolsDocumentationTest.ts === +// Test case for symbols documentation - demonstrates name resolution behavior + +type x = number; // Type symbol in global scope +>x : number +> : ^^^^^^ + +const globalVar = 42; // Value symbol in global scope +>globalVar : 42 +> : ^^ +>42 : 42 +> : ^^ + +function fn(x: string, globalVar: boolean) { // Parameters shadow global symbols +>fn : (x: string, globalVar: boolean) => number +> : ^ ^^ ^^ ^^ ^^^^^^^^^^^ +>x : string +> : ^^^^^^ +>globalVar : boolean +> : ^^^^^^^ + + let y: x = x; // Type 'x' resolves to global type alias, value 'x' resolves to parameter +>y : number +> : ^^^^^^ +>x : string +> : ^^^^^^ + + let z = globalVar; // Resolves to parameter, not global variable +>z : boolean +> : ^^^^^^^ +>globalVar : boolean +> : ^^^^^^^ + + function nested() { +>nested : () => number +> : ^^^^^^^^^^^^ + + let x = "nested"; // Block-scoped variable shadows parameter +>x : string +> : ^^^^^^ +>"nested" : "nested" +> : ^^^^^^^^ + + return x.length; // Resolves to local variable +>x.length : number +> : ^^^^^^ +>x : string +> : ^^^^^^ +>length : number +> : ^^^^^^ + } + + return nested(); +>nested() : number +> : ^^^^^^ +>nested : () => number +> : ^^^^^^^^^^^^ +} + +interface I { // Interface symbol + method(): void; +>method : () => void +> : ^^^^^^ +} + +interface I { // Interface merging - adds to same symbol + prop: string; +>prop : string +> : ^^^^^^ +} + +class C implements I { // Class symbol, can merge with interface +>C : C +> : ^ + + prop = "test"; +>prop : string +> : ^^^^^^ +>"test" : "test" +> : ^^^^^^ + + method() {} +>method : () => void +> : ^^^^^^^^^^ +} + +namespace N { // Namespace symbol +>N : typeof N +> : ^^^^^^^^ + + export const value = 1; +>value : 1 +> : ^ +>1 : 1 +> : ^ +} + +namespace N { // Namespace merging +>N : typeof N +> : ^^^^^^^^ + + export function func() {} +>func : () => void +> : ^^^^^^^^^^ +} + +// Test various contexts where name resolution differs +const test1: I = new C(); // 'I' resolves to merged interface, 'C' resolves to class +>test1 : I +> : ^ +>new C() : C +> : ^ +>C : typeof C +> : ^^^^^^^^ + +const test2 = N.value; // 'N' resolves to merged namespace +>test2 : 1 +> : ^ +>N.value : 1 +> : ^ +>N : typeof N +> : ^^^^^^^^ +>value : 1 +> : ^ + +const test3 = N.func(); // Same namespace, different export +>test3 : void +> : ^^^^ +>N.func() : void +> : ^^^^ +>N.func : () => void +> : ^^^^^^^^^^ +>N : typeof N +> : ^^^^^^^^ +>func : () => void +> : ^^^^^^^^^^ + diff --git a/tests/cases/compiler/symbolsDocumentationTest.ts b/tests/cases/compiler/symbolsDocumentationTest.ts new file mode 100644 index 0000000000000..1f984e43ffba0 --- /dev/null +++ b/tests/cases/compiler/symbolsDocumentationTest.ts @@ -0,0 +1,42 @@ +// Test case for symbols documentation - demonstrates name resolution behavior + +type x = number; // Type symbol in global scope +const globalVar = 42; // Value symbol in global scope + +function fn(x: string, globalVar: boolean) { // Parameters shadow global symbols + let y: x = x; // Type 'x' resolves to global type alias, value 'x' resolves to parameter + let z = globalVar; // Resolves to parameter, not global variable + + function nested() { + let x = "nested"; // Block-scoped variable shadows parameter + return x.length; // Resolves to local variable + } + + return nested(); +} + +interface I { // Interface symbol + method(): void; +} + +interface I { // Interface merging - adds to same symbol + prop: string; +} + +class C implements I { // Class symbol, can merge with interface + prop = "test"; + method() {} +} + +namespace N { // Namespace symbol + export const value = 1; +} + +namespace N { // Namespace merging + export function func() {} +} + +// Test various contexts where name resolution differs +const test1: I = new C(); // 'I' resolves to merged interface, 'C' resolves to class +const test2 = N.value; // 'N' resolves to merged namespace +const test3 = N.func(); // Same namespace, different export \ No newline at end of file