@alloy-js/core 0.20.0-dev.4 → 0.20.0-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/src/binder.d.ts +62 -38
  2. package/dist/src/binder.d.ts.map +1 -1
  3. package/dist/src/binder.js +214 -173
  4. package/dist/src/components/Declaration.d.ts +2 -2
  5. package/dist/src/components/Declaration.d.ts.map +1 -1
  6. package/dist/src/components/Declaration.js +8 -2
  7. package/dist/src/components/MemberDeclaration.d.ts +2 -2
  8. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  9. package/dist/src/components/MemberDeclaration.js +9 -5
  10. package/dist/src/components/MemberScope.d.ts +30 -13
  11. package/dist/src/components/MemberScope.d.ts.map +1 -1
  12. package/dist/src/components/MemberScope.js +37 -15
  13. package/dist/src/components/Output.d.ts.map +1 -1
  14. package/dist/src/components/Output.js +2 -5
  15. package/dist/src/components/ReferenceOrContent.d.ts +1 -1
  16. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -1
  17. package/dist/src/components/Scope.d.ts +5 -5
  18. package/dist/src/components/Scope.d.ts.map +1 -1
  19. package/dist/src/components/Scope.js +9 -5
  20. package/dist/src/context/member-scope.d.ts +7 -8
  21. package/dist/src/context/member-scope.d.ts.map +1 -1
  22. package/dist/src/context/member-scope.js +5 -5
  23. package/dist/src/context/name-policy.d.ts.map +1 -1
  24. package/dist/src/context/name-policy.js +3 -0
  25. package/dist/src/context/scope.d.ts +1 -0
  26. package/dist/src/context/scope.d.ts.map +1 -1
  27. package/dist/src/context/scope.js +7 -0
  28. package/dist/src/inspect.browser.d.ts +5 -0
  29. package/dist/src/inspect.browser.d.ts.map +1 -0
  30. package/dist/src/inspect.browser.js +5 -0
  31. package/dist/src/inspect.d.ts +2 -0
  32. package/dist/src/inspect.d.ts.map +1 -0
  33. package/dist/src/inspect.js +1 -0
  34. package/dist/src/name-policy.d.ts +11 -0
  35. package/dist/src/name-policy.d.ts.map +1 -1
  36. package/dist/src/name-policy.js +3 -0
  37. package/dist/src/reactive-union-set.d.ts.map +1 -1
  38. package/dist/src/reactive-union-set.js +12 -8
  39. package/dist/src/refkey.d.ts +39 -3
  40. package/dist/src/refkey.d.ts.map +1 -1
  41. package/dist/src/refkey.js +52 -8
  42. package/dist/src/symbols/basic-scope.d.ts +14 -0
  43. package/dist/src/symbols/basic-scope.d.ts.map +1 -0
  44. package/dist/src/symbols/basic-scope.js +20 -0
  45. package/dist/src/symbols/basic-symbol.d.ts +19 -0
  46. package/dist/src/symbols/basic-symbol.d.ts.map +1 -0
  47. package/dist/src/symbols/basic-symbol.js +28 -0
  48. package/dist/src/symbols/index.d.ts +3 -1
  49. package/dist/src/symbols/index.d.ts.map +1 -1
  50. package/dist/src/symbols/index.js +3 -1
  51. package/dist/src/symbols/output-scope.d.ts +70 -41
  52. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  53. package/dist/src/symbols/output-scope.js +98 -130
  54. package/dist/src/symbols/output-space.d.ts +25 -0
  55. package/dist/src/symbols/output-space.d.ts.map +1 -0
  56. package/dist/src/symbols/output-space.js +35 -0
  57. package/dist/src/symbols/output-symbol.d.ts +213 -37
  58. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  59. package/dist/src/symbols/output-symbol.js +323 -203
  60. package/dist/src/symbols/symbol-flow.d.ts +1 -1
  61. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  62. package/dist/src/symbols/symbol-flow.js +22 -7
  63. package/dist/src/symbols/symbol-slot.d.ts +27 -9
  64. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  65. package/dist/src/symbols/symbol-slot.js +20 -4
  66. package/dist/src/symbols/symbol-table.d.ts +19 -8
  67. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  68. package/dist/src/symbols/symbol-table.js +65 -16
  69. package/dist/src/tracer.d.ts +15 -3
  70. package/dist/src/tracer.d.ts.map +1 -1
  71. package/dist/src/tracer.js +39 -63
  72. package/dist/test/components/declaration.test.js +9 -14
  73. package/dist/test/components/reference-or-content.test.js +2 -2
  74. package/dist/test/symbols/output-scope.test.js +33 -198
  75. package/dist/test/symbols/output-symbol.test.js +139 -385
  76. package/dist/test/symbols/resolution.test.js +431 -114
  77. package/dist/test/symbols/symbol-table.test.d.ts +2 -0
  78. package/dist/test/symbols/symbol-table.test.d.ts.map +1 -0
  79. package/dist/test/symbols/symbol-table.test.js +14 -0
  80. package/dist/test/symbols/utils.d.ts +10 -24
  81. package/dist/test/symbols/utils.d.ts.map +1 -1
  82. package/dist/test/symbols/utils.js +23 -45
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +4 -2
  85. package/src/binder.ts +348 -273
  86. package/src/components/Declaration.tsx +13 -3
  87. package/src/components/MemberDeclaration.tsx +15 -8
  88. package/src/components/MemberScope.tsx +61 -20
  89. package/src/components/Output.tsx +0 -4
  90. package/src/components/Scope.tsx +16 -9
  91. package/src/context/member-scope.ts +10 -10
  92. package/src/context/name-policy.ts +3 -0
  93. package/src/context/scope.ts +9 -0
  94. package/src/inspect.browser.ts +6 -0
  95. package/src/inspect.ts +1 -0
  96. package/src/name-policy.ts +14 -0
  97. package/src/reactive-union-set.ts +14 -8
  98. package/src/refkey.ts +88 -14
  99. package/src/symbols/basic-scope.ts +23 -0
  100. package/src/symbols/basic-symbol.ts +32 -0
  101. package/src/symbols/index.ts +3 -1
  102. package/src/symbols/output-scope.ts +131 -170
  103. package/src/symbols/output-space.ts +49 -0
  104. package/src/symbols/output-symbol.ts +434 -258
  105. package/src/symbols/symbol-flow.ts +38 -9
  106. package/src/symbols/symbol-slot.tsx +46 -8
  107. package/src/symbols/symbol-table.ts +95 -21
  108. package/src/tracer.ts +53 -83
  109. package/temp/api.json +5559 -3079
  110. package/test/components/declaration.test.tsx +6 -19
  111. package/test/components/reference-or-content.test.tsx +2 -2
  112. package/test/symbols/output-scope.test.ts +33 -125
  113. package/test/symbols/output-symbol.test.ts +128 -348
  114. package/test/symbols/resolution.test.ts +530 -117
  115. package/test/symbols/symbol-table.test.ts +15 -0
  116. package/test/symbols/utils.ts +38 -74
  117. package/tsdoc.json +4 -0
  118. package/dist/src/slot.d.ts +0 -15
  119. package/dist/src/slot.d.ts.map +0 -1
  120. package/dist/src/slot.js +0 -50
  121. package/dist/src/symbols/flags.d.ts +0 -70
  122. package/dist/src/symbols/flags.d.ts.map +0 -1
  123. package/dist/src/symbols/flags.js +0 -72
  124. package/dist/test/components/slot.test.d.ts +0 -2
  125. package/dist/test/components/slot.test.d.ts.map +0 -1
  126. package/dist/test/components/slot.test.js +0 -134
  127. package/src/slot.ts +0 -89
  128. package/src/symbols/flags.ts +0 -82
  129. package/test/components/slot.test.tsx +0 -174
package/src/binder.ts CHANGED
@@ -1,14 +1,12 @@
1
- import { computed, ref, Ref, ShallowRef, shallowRef } from "@vue/reactivity";
2
- import { useMemberScope } from "./context/member-scope.js";
1
+ import { computed, Ref, ShallowRef, shallowRef } from "@vue/reactivity";
2
+ import { useMemberContext } from "./context/member-scope.js";
3
3
  import { useScope } from "./context/scope.js";
4
- import { effect, untrack } from "./reactivity.js";
5
- import { refkey, Refkey } from "./refkey.js";
6
- import { OutputSymbolFlags } from "./symbols/flags.js";
4
+ import { effect } from "./reactivity.js";
5
+ import { isMemberRefkey, refkey, Refkey } from "./refkey.js";
7
6
  import { OutputScope } from "./symbols/output-scope.js";
8
7
  import { type OutputSymbol } from "./symbols/output-symbol.js";
9
8
  import {
10
9
  formatRefkeys,
11
- formatSymbol,
12
10
  formatSymbolName,
13
11
  trace,
14
12
  TracePhase,
@@ -41,52 +39,18 @@ export interface Binder {
41
39
  TSymbol extends OutputSymbol = OutputSymbol,
42
40
  >(
43
41
  currentScope: TScope | undefined,
44
- currentMemberScope: TScope | undefined,
45
42
  key: Refkey,
43
+ options?: ResolveDeclarationByKeyOptions<TScope, TSymbol>,
46
44
  ): Ref<ResolutionResult<TScope, TSymbol> | undefined>;
47
45
 
48
- getSymbolForRefkey<TSymbol extends OutputSymbol>(
49
- refkey: Refkey,
50
- ): Ref<TSymbol | undefined>;
51
-
52
46
  /**
53
- * Find a symbol with a given name in the given scope. Returns a ref
54
- * for the symbol, such that when the symbol is available, the ref value
55
- * will update.
47
+ * Get a ref to the symbol associated with the given refkey. The value of the
48
+ * ref is undefined if the symbol has not been created yet.
56
49
  */
57
- findSymbolName<
58
- TScope extends OutputScope = OutputScope,
59
- TSymbol extends OutputSymbol = OutputSymbol,
60
- >(
61
- currentScope: TScope | undefined,
62
- name: string,
50
+ getSymbolForRefkey<TSymbol extends OutputSymbol>(
51
+ refkey: Refkey,
63
52
  ): Ref<TSymbol | undefined>;
64
53
 
65
- findScopeName<TScope extends OutputScope = OutputScope>(
66
- currentScope: TScope | undefined,
67
- name: string,
68
- ): Ref<TScope | undefined>;
69
-
70
- /**
71
- * Resolve a fully qualified name to a symbol. Access a nested scope by name
72
- * with `::`, a nested static member with `.` and a nested instance member
73
- * with `#`.
74
- *
75
- * Per-language packages may provide their own resolveFQN function that uses
76
- * syntax more natural to that language.
77
- */
78
- resolveFQN<
79
- TScope extends OutputScope = OutputScope,
80
- TSymbol extends OutputSymbol = OutputSymbol,
81
- >(
82
- fqn: string,
83
- ): Ref<TSymbol | TScope | undefined>;
84
-
85
- /**
86
- * The global scope. This is the root scope for all symbols.
87
- */
88
- globalScope: OutputScope;
89
-
90
54
  /**
91
55
  * The name conflict resolver to use for this binder.
92
56
  */
@@ -119,7 +83,7 @@ export interface Binder {
119
83
  * scope: global scope
120
84
  * ├── scope: namespace scope 1
121
85
  * │ └── symbol: foo
122
- * │ └── static member scope
86
+ * │ └── static members
123
87
  * │ └── symbol: bar
124
88
  * └── scope: namespace scope 2
125
89
  * └── (resolve bar from here)
@@ -139,11 +103,21 @@ export interface ResolutionResult<
139
103
  TSymbol extends OutputSymbol,
140
104
  > {
141
105
  /**
142
- * The symbol for the resolved declaration.
106
+ * The resolved symbol. May be declared in a lexical scope or be a member symbol.
107
+ */
108
+ symbol: TSymbol;
109
+
110
+ /**
111
+ * When the symbol is a member symbol, this is the symbol of the lexical
112
+ * declaration which contains this member symbol, either as one of its own
113
+ * member symbols, or as a member of one of its members.
114
+ *
115
+ * When the symbol is a non-member symbol, this is the same as `symbol`.
143
116
  */
144
- targetDeclaration: TSymbol;
117
+ lexicalDeclaration: TSymbol;
118
+
145
119
  /**
146
- * The scopes between the common scope and the reference
120
+ * The scopes between the common scope and the reference.
147
121
  */
148
122
  pathUp: TScope[];
149
123
 
@@ -153,21 +127,78 @@ export interface ResolutionResult<
153
127
  pathDown: TScope[];
154
128
 
155
129
  /**
156
- * The scope which contains both the reference and the declaration.
130
+ * The scopes from the root to scope of the lexical declaration.
131
+ */
132
+ fullSymbolPath: TScope[];
133
+
134
+ /**
135
+ * The scopes from the root to the scope of the reference.
136
+ */
137
+ fullReferencePath: TScope[];
138
+
139
+ /**
140
+ * The lexical scope which contains both the reference and the lexical
141
+ * declaration. Undefined when they do not share a common scope.
157
142
  */
158
143
  commonScope: TScope | undefined;
159
144
 
160
145
  /**
161
146
  * When resolving a member symbol, this is the path of symbols that lead from
162
- * the base declaration to the member symbol.
147
+ * the lexical declaration to the member symbol.
163
148
  */
164
- memberPath?: TSymbol[];
149
+ memberPath: TSymbol[];
150
+ }
151
+
152
+ /**
153
+ * Describes a member in a member access chain, tracking both the symbol
154
+ * and whether this specific member was accessed via a memberRefkey.
155
+ */
156
+ export interface MemberDescriptor {
157
+ symbol: OutputSymbol;
158
+ isMemberAccess: boolean;
165
159
  }
166
160
 
167
161
  export interface NameConflictResolver {
168
162
  (name: string, symbols: OutputSymbol[]): void;
169
163
  }
170
164
 
165
+ /**
166
+ * The context for a member resolution. This is used to properly resolve a
167
+ * member in the MemberResolver.
168
+ */
169
+ export interface MemberResolutionContext<TScope extends OutputScope> {
170
+ /**
171
+ * The scopes that the member reference occurred in.
172
+ */
173
+ referencePath: TScope[];
174
+
175
+ /**
176
+ * Whether we are using member access e.g. via `memberRefkey`.
177
+ * This is true when the member was resolved using a memberRefkey,
178
+ * which may carry additional metadata about the member access in the future.
179
+ */
180
+ isMemberAccess: boolean;
181
+ }
182
+
183
+ /**
184
+ *
185
+ */
186
+ export interface MemberResolver<
187
+ TScope extends OutputScope,
188
+ TSymbol extends OutputSymbol,
189
+ > {
190
+ (
191
+ owner: TSymbol,
192
+ member: TSymbol,
193
+ context: MemberResolutionContext<TScope>,
194
+ ): void;
195
+ }
196
+ export interface ResolveDeclarationByKeyOptions<
197
+ TScope extends OutputScope,
198
+ TSymbol extends OutputSymbol,
199
+ > {
200
+ memberResolver?: MemberResolver<TScope, TSymbol>;
201
+ }
171
202
  export interface BinderOptions {
172
203
  nameConflictResolver?: NameConflictResolver;
173
204
  }
@@ -176,21 +207,12 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
176
207
  const binder: Binder = {
177
208
  resolveDeclarationByKey,
178
209
  getSymbolForRefkey,
179
- findSymbolName,
180
- findScopeName,
181
- resolveFQN: resolveFQN as any,
182
- globalScope: undefined as any,
183
210
  notifyScopeCreated,
184
211
  notifySymbolCreated,
185
212
  notifySymbolDeleted,
186
213
  nameConflictResolver: options.nameConflictResolver,
187
214
  };
188
215
 
189
- binder.globalScope = new OutputScope("<global>", {
190
- binder,
191
- kind: "global",
192
- });
193
-
194
216
  const knownDeclarations = new Map<Refkey, OutputSymbol>();
195
217
  const waitingDeclarations = new Map<Refkey, Ref<OutputSymbol | undefined>>();
196
218
  const waitingSymbolNames = new Map<
@@ -228,75 +250,112 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
228
250
  }
229
251
  }
230
252
 
231
- function hasTransientScope(symbol: OutputSymbol) {
232
- let sym: OutputSymbol | undefined = symbol;
233
- let transient = false;
234
- while (sym) {
235
- if (sym.flags & OutputSymbolFlags.Transient) {
236
- transient = true;
237
- break;
238
- }
239
- if (sym.flags & ~OutputSymbolFlags.Member) {
240
- break;
241
- }
242
-
243
- sym = sym.scope.owner;
244
- }
245
-
246
- return transient;
247
- }
248
253
  function buildResult<
249
254
  TScope extends OutputScope = OutputScope,
250
255
  TSymbol extends OutputSymbol = OutputSymbol,
251
256
  >(
252
257
  currentScope: TScope | undefined,
253
- currentMemberScope: TScope | undefined,
254
258
  targetDeclarationBase: TSymbol,
255
259
  ): ResolutionResult<TScope, TSymbol> {
256
- trace(TracePhase.resolve.success, () => {
257
- return `Resolved ${formatRefkeys(targetDeclarationBase.refkeys)} to ${formatSymbol(targetDeclarationBase)}`;
258
- });
259
- if (targetDeclarationBase.flags & OutputSymbolFlags.InstanceMember) {
260
- // todo: handle referencing nested objects by refkey
261
- return {
262
- pathUp: [],
263
- pathDown: [],
264
- memberPath: [targetDeclarationBase],
265
- commonScope: currentMemberScope,
266
- targetDeclaration: targetDeclarationBase,
267
- };
260
+ const { memberPath: targetMemberPath, scopeChain: targetChain } =
261
+ scopeAndMemberChain<TScope, TSymbol>(targetDeclarationBase);
262
+
263
+ let targetLexicalDeclaration =
264
+ targetMemberPath && targetMemberPath.length > 0 ?
265
+ (targetMemberPath[0].ownerSymbol! as TSymbol)
266
+ : targetDeclarationBase;
267
+ // when we are resolving from a scope which is a member scope and might have
268
+ // member scope parents, and any symbols in the member path are members of
269
+ // the member scope's owner symbol (i.e., those symbols are in scope where
270
+ // the reference is made), we replace the target member path with an entry
271
+ // on the scope chain.
272
+
273
+ // So, first we find all the owner symbols for any member scopes in scope
274
+ // for the reference.
275
+ const referenceChain = scopeChain(currentScope);
276
+
277
+ const inScopeSymbols = new Map<TSymbol, TScope>();
278
+ for (const scope of referenceChain) {
279
+ if (scope.isMemberScope) {
280
+ inScopeSymbols.set(scope.ownerSymbol! as TSymbol, scope);
281
+ }
282
+ }
283
+
284
+ // then if the lexical declaration symbol's members are in scope, remove the
285
+ // symbol from the member path and add its corresponding member scope to the
286
+ // target chain.
287
+ while (
288
+ targetMemberPath.length > 0 &&
289
+ inScopeSymbols.has(targetLexicalDeclaration)
290
+ ) {
291
+ targetChain.push(inScopeSymbols.get(targetLexicalDeclaration)!);
292
+ targetLexicalDeclaration = targetMemberPath.shift()!;
293
+ }
294
+
295
+ // Now we replace any scopes in the target chain with corresponding scopes
296
+ // from the reference chain.
297
+ for (const [index, scope] of targetChain.entries()) {
298
+ if (inScopeSymbols.has(scope.ownerSymbol! as TSymbol)) {
299
+ targetChain[index] = inScopeSymbols.get(scope.ownerSymbol! as TSymbol)!;
300
+ }
301
+ }
302
+
303
+ // Next we look for member scopes in the target chain that correspond to
304
+ // member scopes in the reference chain. We splice the reference chain into
305
+ // the target chain at that point. This ensures that we establish the proper
306
+ // common scope and path up/down even if the reference chain has additional
307
+ // scopes above it (e.g. a scope for the current source file).
308
+ const commonMemberContainer = targetChain.findIndex(
309
+ (scope) =>
310
+ scope.isMemberScope && inScopeSymbols.has(scope.ownerSymbol as TSymbol),
311
+ );
312
+
313
+ if (commonMemberContainer > -1) {
314
+ const sourceLocation = referenceChain.findIndex(
315
+ (scope) =>
316
+ scope.isMemberScope &&
317
+ scope.ownerSymbol === targetChain[commonMemberContainer].ownerSymbol,
318
+ );
319
+
320
+ // source location is guaranteed to exist at this point.
321
+ targetChain.splice(
322
+ 0,
323
+ commonMemberContainer + 1,
324
+ ...referenceChain.slice(0, sourceLocation + 1),
325
+ );
268
326
  }
269
327
 
270
- const { memberPath, scopeChain: targetChain } = scopeAndMemberChain<
271
- TScope,
272
- TSymbol
273
- >(targetDeclarationBase);
274
- const currentChain = scopeChain(currentScope);
328
+ // Now that we have the target chain and scopes, we can determine the common
329
+ // scopes and paths.
275
330
  let diffStart = 0;
276
331
  while (
277
332
  targetChain[diffStart] &&
278
- currentChain[diffStart] &&
279
- targetChain[diffStart] === currentChain[diffStart]
333
+ referenceChain[diffStart] &&
334
+ targetChain[diffStart] === referenceChain[diffStart]
280
335
  ) {
281
336
  diffStart++;
282
337
  }
283
-
284
- const pathUp = currentChain.slice(diffStart);
338
+ const pathUp = referenceChain.slice(diffStart);
285
339
  const pathDown = targetChain.slice(diffStart);
286
- const commonScope = targetChain[diffStart - 1] ?? null;
340
+ const commonScope = targetChain[diffStart - 1] ?? undefined;
287
341
 
288
342
  return {
289
343
  pathUp,
290
344
  pathDown,
291
- memberPath,
345
+ memberPath: targetMemberPath,
292
346
  commonScope,
293
- targetDeclaration:
294
- memberPath && memberPath.length > 0 ?
295
- memberPath.at(0)!
296
- : targetDeclarationBase,
347
+ symbol: targetDeclarationBase,
348
+ lexicalDeclaration: targetLexicalDeclaration,
349
+ fullSymbolPath: targetChain,
350
+ fullReferencePath: referenceChain,
297
351
  };
298
352
  }
299
353
 
354
+ /**
355
+ * Walk from the provided symbol up to the non-member symbol. This constitutes
356
+ * the member path. Then, walk from the first non-member symbol's scope up to
357
+ * the global scope. This constitutes the scope chain.
358
+ */
300
359
  function scopeAndMemberChain<
301
360
  TScope extends OutputScope,
302
361
  TSymbol extends OutputSymbol,
@@ -305,15 +364,14 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
305
364
  memberPath: [] as TSymbol[],
306
365
  scopeChain: [] as TScope[],
307
366
  };
308
-
309
367
  let currentSymbol = symbol;
310
- while (currentSymbol.flags & OutputSymbolFlags.StaticMember) {
311
- result.memberPath.unshift(currentSymbol);
312
- currentSymbol = currentSymbol.scope.owner! as TSymbol;
313
- }
314
368
 
315
- if (symbol.flags & OutputSymbolFlags.StaticMember) {
316
- result.memberPath.unshift(currentSymbol);
369
+ if (currentSymbol.isMemberSymbol) {
370
+ result.memberPath = [];
371
+ while (currentSymbol.isMemberSymbol) {
372
+ result.memberPath.unshift(currentSymbol);
373
+ currentSymbol = currentSymbol.ownerSymbol as TSymbol;
374
+ }
317
375
  }
318
376
 
319
377
  const startScope = currentSymbol.scope as TScope;
@@ -339,7 +397,30 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
339
397
  return waitingDeclarations.get(refkey)! as ShallowRef<TSymbol>;
340
398
  }
341
399
 
342
- const symbolRef = shallowRef<TSymbol | undefined>();
400
+ let symbolRef: ShallowRef<TSymbol | undefined>;
401
+
402
+ if (isMemberRefkey(refkey)) {
403
+ const baseSymbolRef: ShallowRef<TSymbol | undefined> =
404
+ getSymbolForRefkey<TSymbol>(refkey.base);
405
+ const memberSymbolRef: ShallowRef<TSymbol | undefined> =
406
+ getSymbolForRefkey<TSymbol>(refkey.member);
407
+
408
+ symbolRef = computed(() => {
409
+ // even though we don't necessarily need the base symbol to be available
410
+ // yet (the member symbol might already be declared on a type), we wait
411
+ // to resolve the member refkey until the base symbol is available.
412
+ const baseSymbol = baseSymbolRef.value;
413
+ const memberSymbol = memberSymbolRef.value;
414
+ if (!baseSymbol || !memberSymbol) {
415
+ return undefined;
416
+ }
417
+
418
+ return memberSymbol;
419
+ }) as ShallowRef<TSymbol | undefined>;
420
+ } else {
421
+ symbolRef = shallowRef<TSymbol | undefined>();
422
+ }
423
+
343
424
  waitingDeclarations.set(refkey, symbolRef);
344
425
  if (knownDeclarations.has(refkey)) {
345
426
  symbolRef.value = knownDeclarations.get(refkey) as TSymbol;
@@ -347,15 +428,23 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
347
428
  return symbolRef;
348
429
  }
349
430
 
431
+ /**
432
+ * There are two ways to reference a member symbol - directly via its refkey,
433
+ * and via a member refkey. In the former case, we just find the member
434
+ * symbol, and then compute its path directly from there. In hte latter case,
435
+ * we need to find the base of the member expression, and then add the members
436
+ * from the (possibly nested) member refkey to result.
437
+ */
350
438
  function resolveDeclarationByKey<
351
439
  TScope extends OutputScope = OutputScope,
352
440
  TSymbol extends OutputSymbol = OutputSymbol,
353
441
  >(
354
442
  currentScope: TScope | undefined,
355
- currentMemberScope: TScope | undefined,
356
443
  refkey: Refkey,
444
+ options: ResolveDeclarationByKeyOptions<TScope, TSymbol> = {},
357
445
  ): ShallowRef<ResolutionResult<TScope, TSymbol> | undefined> {
358
446
  const resolvedSymbol = getSymbolForRefkey(refkey);
447
+
359
448
  return computed(() => {
360
449
  trace(
361
450
  TracePhase.resolve.pending,
@@ -374,33 +463,147 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
374
463
  () =>
375
464
  `${formatRefkeys(refkey)} resolved to ${formatSymbolName(symbol)}.`,
376
465
  );
377
- if (hasTransientScope(symbol)) {
466
+ if (symbol.isTransient) {
378
467
  trace(
379
468
  TracePhase.resolve.failure,
380
- () => `Symbol ${formatSymbolName(symbol)} in transient scope.`,
469
+ () => `Symbol ${formatSymbolName(symbol)} is transient.`,
381
470
  );
382
471
  return undefined;
383
472
  }
384
473
 
385
- trace(
386
- TracePhase.resolve.success,
387
- () =>
388
- `${formatRefkeys(refkey)} successfully resolved to ${formatSymbolName(symbol)}.`,
389
- );
390
- return buildResult(currentScope, currentMemberScope, symbol);
474
+ const chain = scopeChain(symbol.scope);
475
+ if (chain.some((scope) => scope.isTransient)) {
476
+ trace(
477
+ TracePhase.resolve.failure,
478
+ () => `Symbol ${formatSymbolName(symbol)} is in a transient scope.`,
479
+ );
480
+ return undefined;
481
+ }
482
+
483
+ let result: ResolutionResult<TScope, TSymbol>;
484
+ let memberDescriptorsFromRefkey: MemberDescriptor[];
485
+
486
+ if (isMemberRefkey(refkey)) {
487
+ memberDescriptorsFromRefkey = getMemberPathFromRefkey(refkey);
488
+
489
+ result = buildResult(
490
+ currentScope,
491
+ memberDescriptorsFromRefkey[0].symbol as TSymbol,
492
+ );
493
+ } else {
494
+ memberDescriptorsFromRefkey = [];
495
+ result = buildResult(currentScope, symbol);
496
+ }
497
+
498
+ // When we have member descriptors, this first entry is already part of the result due to passing it
499
+ // to buildResult above, so we don't need it here.
500
+ const newMemberPathDescriptors = memberDescriptorsFromRefkey.slice(1);
501
+ const allDescriptors = [
502
+ ...result.memberPath.map((s) => ({ symbol: s, isMemberAccess: false })),
503
+ ...newMemberPathDescriptors,
504
+ ];
505
+
506
+ // update the member path and resolved symbol from our member descriptors
507
+ // (if we have them)
508
+ if (memberDescriptorsFromRefkey.length > 0) {
509
+ for (const descriptor of newMemberPathDescriptors) {
510
+ result.memberPath.push(descriptor.symbol as TSymbol);
511
+ }
512
+ result.symbol = memberDescriptorsFromRefkey.at(-1)!.symbol as TSymbol;
513
+ }
514
+
515
+ // a subcomputed here ensures we don't lose the progress above When
516
+ // we fail to resolve because a type isn't available yet.
517
+ return computed(() => {
518
+ // resolve each member in the member path
519
+ let currentBase = result.lexicalDeclaration;
520
+
521
+ for (const descriptor of allDescriptors) {
522
+ const member = descriptor.symbol as TSymbol;
523
+ if (currentBase.isTyped && !currentBase.hasTypeSymbol) {
524
+ trace(
525
+ TracePhase.resolve.pending,
526
+ () =>
527
+ `${formatRefkeys(refkey)} needs type information from a parent type.`,
528
+ );
529
+ // waiting for type
530
+ return undefined;
531
+ }
532
+
533
+ resolveMember(currentBase, member, options.memberResolver, {
534
+ referencePath: result.fullReferencePath,
535
+ isMemberAccess: descriptor.isMemberAccess,
536
+ });
537
+
538
+ currentBase = member;
539
+ }
540
+
541
+ trace(
542
+ TracePhase.resolve.success,
543
+ () =>
544
+ `${formatRefkeys(refkey)} successfully resolved to ${formatSymbolName(symbol)}.`,
545
+ );
546
+ return result;
547
+ }).value;
391
548
  });
392
549
  }
393
550
 
394
- function notifySymbolCreated(symbol: OutputSymbol): void {
395
- if (symbol.flags & OutputSymbolFlags.Transient) {
396
- // just ignore transient symbols.
397
- return;
551
+ /**
552
+ * Extracts member descriptors from a refkey, tracking which members
553
+ * were accessed via memberRefkey for proper member resolution context.
554
+ */
555
+ function getMemberPathFromRefkey(refkey: Refkey): MemberDescriptor[] {
556
+ if (isMemberRefkey(refkey)) {
557
+ return [
558
+ ...getMemberPathFromRefkey(refkey.base),
559
+ {
560
+ symbol: getSymbolForRefkey(refkey.member).value!,
561
+ isMemberAccess: true,
562
+ },
563
+ ];
564
+ }
565
+
566
+ return [
567
+ {
568
+ symbol: getSymbolForRefkey(refkey).value!,
569
+ isMemberAccess: false,
570
+ },
571
+ ];
572
+ }
573
+
574
+ function resolveMember(
575
+ base: OutputSymbol,
576
+ member: OutputSymbol,
577
+ memberResolver: MemberResolver<any, any> | undefined,
578
+ context: MemberResolutionContext<any>,
579
+ ) {
580
+ if (memberResolver) {
581
+ memberResolver(base, member, context);
582
+ } else {
583
+ // default member resolution
584
+ if (!member.isMemberSymbol) {
585
+ throw new Error(`${formatSymbolName(member)} is not a member symbol.`);
586
+ }
587
+
588
+ const memberOwner = base.hasTypeSymbol ? base.type : base.dealias();
589
+ if (member.ownerSymbol !== memberOwner) {
590
+ throw new Error(
591
+ `${formatSymbolName(
592
+ member,
593
+ )} is not a member of ${formatSymbolName(base)}.`,
594
+ );
595
+ }
398
596
  }
597
+ }
598
+
599
+ function notifySymbolCreated(symbol: OutputSymbol): void {
399
600
  effect<Refkey[]>((oldRefkeys) => {
400
- trace(
401
- TracePhase.resolve.pending,
402
- () => `Notifying resolutions for ${formatRefkeys(symbol.refkeys)}.`,
403
- );
601
+ if (symbol.refkeys) {
602
+ trace(
603
+ TracePhase.resolve.pending,
604
+ () => `Notifying resolutions for ${formatRefkeys(symbol.refkeys)}.`,
605
+ );
606
+ }
404
607
 
405
608
  if (oldRefkeys) {
406
609
  for (const refkey of oldRefkeys) {
@@ -425,8 +628,13 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
425
628
  signal.value = symbol;
426
629
  }
427
630
 
631
+ const scope = symbol.scope;
632
+ if (!scope) {
633
+ continue;
634
+ }
635
+
428
636
  // notify those waiting for this symbol name
429
- const waitingScope = waitingSymbolNames.get(symbol.scope);
637
+ const waitingScope = waitingSymbolNames.get(scope);
430
638
  if (waitingScope) {
431
639
  const waitingName = waitingScope.get(symbol.name);
432
640
  if (waitingName) {
@@ -438,113 +646,6 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
438
646
  return [...symbol.refkeys];
439
647
  });
440
648
  }
441
-
442
- function findSymbolName<TSymbol extends OutputSymbol = OutputSymbol>(
443
- scope: OutputScope | undefined,
444
- name: string,
445
- ): ShallowRef<TSymbol | undefined> {
446
- return untrack(() => {
447
- scope ??= binder.globalScope;
448
- for (const sym of scope.symbols) {
449
- if (sym.name === name) {
450
- return shallowRef(sym) as Ref<TSymbol>;
451
- }
452
- }
453
-
454
- const symRef = shallowRef<OutputSymbol | undefined>(undefined);
455
- if (!waitingSymbolNames.has(scope)) {
456
- waitingSymbolNames.set(scope, new Map());
457
- }
458
- const waiting = waitingSymbolNames.get(scope)!;
459
- waiting.set(name, symRef);
460
- return symRef as Ref<TSymbol | undefined>;
461
- });
462
- }
463
-
464
- function findScopeName<TScope extends OutputScope = OutputScope>(
465
- scope: OutputScope | undefined,
466
- name: string,
467
- ): ShallowRef<TScope | undefined> {
468
- return untrack(() => {
469
- scope ??= binder.globalScope;
470
- for (const child of scope.children) {
471
- if (child.name === name) {
472
- return shallowRef(child) as Ref<TScope>;
473
- }
474
- }
475
-
476
- const scopeRef = shallowRef<OutputScope | undefined>(undefined);
477
- if (!waitingScopeNames.has(scope)) {
478
- waitingScopeNames.set(scope, new Map());
479
- }
480
- const waiting = waitingScopeNames.get(scope)!;
481
- waiting.set(name, scopeRef);
482
-
483
- return scopeRef as Ref<TScope | undefined>;
484
- });
485
- }
486
-
487
- function findScopeOrSymbolName(scope: OutputScope, name: string) {
488
- return untrack(() => {
489
- return computed(() => {
490
- return (
491
- findSymbolName(scope, name).value ?? findScopeName(scope, name).value
492
- );
493
- });
494
- });
495
- }
496
-
497
- function resolveFQN(
498
- fqn: string,
499
- ): Ref<OutputScope | OutputSymbol | undefined> {
500
- const parts = fqn.match(/[^.#]+|[.#]/g);
501
- if (!parts) return ref(undefined);
502
- if (parts.length === 0) return ref(undefined);
503
-
504
- parts.unshift(".");
505
-
506
- return computed(() => {
507
- let base: OutputScope | OutputSymbol | undefined = binder.globalScope;
508
-
509
- for (let i = 0; i < parts.length; i += 2) {
510
- if (base === undefined) {
511
- return;
512
- }
513
-
514
- const op = parts[i];
515
- const name = parts[i + 1];
516
-
517
- if (op === ".") {
518
- if ("originalName" in base) {
519
- if (!base.staticMemberScope) {
520
- return undefined;
521
- }
522
-
523
- base = findSymbolName(
524
- (base as OutputSymbol).staticMemberScope,
525
- name,
526
- ).value;
527
- } else {
528
- base = findScopeOrSymbolName(base, name).value;
529
- }
530
- } else if (op === "#") {
531
- if ("originalName" in base) {
532
- if (!base.instanceMemberScope) {
533
- return undefined;
534
- }
535
- base = findSymbolName(
536
- (base as OutputSymbol).instanceMemberScope,
537
- name,
538
- ).value;
539
- } else {
540
- return undefined;
541
- }
542
- }
543
- }
544
-
545
- return base;
546
- });
547
- }
548
649
  }
549
650
 
550
651
  /**
@@ -560,22 +661,22 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
560
661
  export function resolve<
561
662
  TScope extends OutputScope,
562
663
  TSymbol extends OutputSymbol,
563
- >(refkey: Refkey): Ref<ResolutionResult<TScope, TSymbol>> {
664
+ >(
665
+ refkey: Refkey,
666
+ options: ResolveDeclarationByKeyOptions<TScope, TSymbol> = {},
667
+ ): Ref<ResolutionResult<TScope, TSymbol>> {
564
668
  const scope = useScope();
565
- const memberScope = useMemberScope();
566
- const binder =
567
- scope?.binder ??
568
- memberScope?.instanceMembers?.binder ??
569
- memberScope?.staticMembers?.binder;
669
+ const memberScope = useMemberContext();
670
+ const binder = scope?.binder ?? memberScope?.ownerSymbol.binder;
570
671
 
571
672
  if (!binder) {
572
673
  throw new Error("Can't resolve refkey without a binder");
573
674
  }
574
675
 
575
676
  return binder.resolveDeclarationByKey(
576
- scope,
577
- memberScope?.instanceMembers,
677
+ scope as TScope,
578
678
  refkey,
679
+ options,
579
680
  ) as any;
580
681
  }
581
682
 
@@ -593,29 +694,3 @@ export function getSymbolCreatorSymbol(): typeof createSymbolsSymbol {
593
694
  export interface SymbolCreator {
594
695
  [createSymbolsSymbol](binder: Binder): void;
595
696
  }
596
-
597
- /**
598
- * Use symbol flags to determine the scope in which a symbol with those flags
599
- * should be declared given the current context.
600
- *
601
- * @param flags - The symbol flags to use to determine the default scope.
602
- * @returns an {@link OutputScope} that is the default scope for the given
603
- * flags.
604
- */
605
- export function useDefaultScope(
606
- flags: OutputSymbolFlags = OutputSymbolFlags.None,
607
- ) {
608
- if ((flags & OutputSymbolFlags.Member) === 0) {
609
- return useScope();
610
- } else {
611
- const memberScope = useMemberScope();
612
- if (!memberScope) {
613
- throw new Error("Cannot declare member symbols without a member scope");
614
- }
615
- if (flags & OutputSymbolFlags.InstanceMember) {
616
- return memberScope.instanceMembers;
617
- } else {
618
- return memberScope.staticMembers;
619
- }
620
- }
621
- }