gitnexus 1.6.13-rc.55 → 1.6.13-rc.56
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.
|
@@ -80,6 +80,8 @@ function bindingOf(identifier) {
|
|
|
80
80
|
if (parent === null)
|
|
81
81
|
return null;
|
|
82
82
|
const shadow = { node: identifier, effect: 'shadow' };
|
|
83
|
+
if (inCasePattern(identifier))
|
|
84
|
+
return isCaseCapture(identifier) ? shadow : null;
|
|
83
85
|
switch (parent.type) {
|
|
84
86
|
case 'assignment':
|
|
85
87
|
case 'augmented_assignment':
|
|
@@ -117,11 +119,9 @@ function bindingOf(identifier) {
|
|
|
117
119
|
: null;
|
|
118
120
|
case 'dotted_name': {
|
|
119
121
|
const owner = parent.parent;
|
|
120
|
-
// `import a.b` binds `a
|
|
122
|
+
// `import a.b` binds `a`.
|
|
121
123
|
if (owner?.type === 'import_statement')
|
|
122
124
|
return parent.firstNamedChild?.id === node.id ? shadow : null;
|
|
123
|
-
if (owner?.type === 'case_pattern')
|
|
124
|
-
return parent.namedChildCount === 1 ? shadow : null;
|
|
125
125
|
if (owner?.type !== 'import_from_statement' || !isField(parent, 'name'))
|
|
126
126
|
return null;
|
|
127
127
|
return importsFromBuiltins(owner) ? { node: identifier, effect: 'builtin' } : shadow;
|
|
@@ -174,9 +174,145 @@ function descriptorBindings(node) {
|
|
|
174
174
|
const redirect = redirected.get(`${found.text}@${scopeOf(found)?.id}`) ?? null;
|
|
175
175
|
add(found.text, { ...binding, redirect });
|
|
176
176
|
}
|
|
177
|
+
addHelperCalls(tree.rootNode, bindings, redirected);
|
|
177
178
|
bindingsByTree.set(tree, bindings);
|
|
178
179
|
return bindings;
|
|
179
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* A bare module-level call to a same-file helper that always restores a
|
|
183
|
+
* descriptor name through `global` runs that restore at the call. Record it
|
|
184
|
+
* there as a module binding, so the call's position orders it.
|
|
185
|
+
*/
|
|
186
|
+
function addHelperCalls(root, bindings, redirected) {
|
|
187
|
+
// Helper function name -> its `def` and, per descriptor name, the restore
|
|
188
|
+
// its call performs.
|
|
189
|
+
const helpers = new Map();
|
|
190
|
+
for (const [name, list] of bindings) {
|
|
191
|
+
const byHelper = new Map();
|
|
192
|
+
for (const binding of list) {
|
|
193
|
+
if (binding.redirect !== 'global')
|
|
194
|
+
continue;
|
|
195
|
+
const fn = scopeOf(binding.node);
|
|
196
|
+
if (fn === null)
|
|
197
|
+
continue;
|
|
198
|
+
const entry = byHelper.get(fn.id);
|
|
199
|
+
if (entry === undefined)
|
|
200
|
+
byHelper.set(fn.id, { fn, globals: [binding] });
|
|
201
|
+
else
|
|
202
|
+
entry.globals.push(binding);
|
|
203
|
+
}
|
|
204
|
+
for (const { fn, globals } of byHelper.values()) {
|
|
205
|
+
const effect = restoreOnCall(fn, globals);
|
|
206
|
+
const helper = fn.childForFieldName('name')?.text;
|
|
207
|
+
if (effect === null || helper === undefined)
|
|
208
|
+
continue;
|
|
209
|
+
const entry = helpers.get(helper) ?? { fn, restores: new Map() };
|
|
210
|
+
entry.restores.set(name, effect);
|
|
211
|
+
helpers.set(helper, entry);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (helpers.size === 0)
|
|
215
|
+
return;
|
|
216
|
+
// A call only denotes the helper when its `def` is the name's one module
|
|
217
|
+
// binding. A local of the same name elsewhere cannot rebind it.
|
|
218
|
+
if (root.descendantsOfType('wildcard_import').length > 0)
|
|
219
|
+
return;
|
|
220
|
+
const bindingCounts = new Map();
|
|
221
|
+
for (const found of root.descendantsOfType('identifier')) {
|
|
222
|
+
if (!helpers.has(found.text) || bindingOf(found) === null)
|
|
223
|
+
continue;
|
|
224
|
+
const scope = scopeOf(found);
|
|
225
|
+
if (scope !== null && redirected.get(`${found.text}@${scope.id}`) !== 'global')
|
|
226
|
+
continue;
|
|
227
|
+
bindingCounts.set(found.text, (bindingCounts.get(found.text) ?? 0) + 1);
|
|
228
|
+
}
|
|
229
|
+
for (const [helper, count] of bindingCounts)
|
|
230
|
+
if (count > 1)
|
|
231
|
+
helpers.delete(helper);
|
|
232
|
+
const touched = new Set();
|
|
233
|
+
for (const statement of root.namedChildren) {
|
|
234
|
+
// Only a call that is the whole statement surely runs when reached.
|
|
235
|
+
if (statement.type !== 'expression_statement' || statement.namedChildCount !== 1)
|
|
236
|
+
continue;
|
|
237
|
+
const call = statement.firstNamedChild;
|
|
238
|
+
const callee = call?.type === 'call' ? call.childForFieldName('function') : null;
|
|
239
|
+
const helper = callee?.type === 'identifier' ? helpers.get(callee.text) : undefined;
|
|
240
|
+
// The call reaches the helper only once its `def` statement has run, and
|
|
241
|
+
// an argument-free call binds only when every parameter is optional.
|
|
242
|
+
if (helper === undefined || statement.startIndex < helper.fn.endIndex)
|
|
243
|
+
continue;
|
|
244
|
+
if ((call?.childForFieldName('arguments')?.namedChildCount ?? 0) > 0)
|
|
245
|
+
continue;
|
|
246
|
+
for (const [name, effect] of helper.restores) {
|
|
247
|
+
const list = bindings.get(name);
|
|
248
|
+
if (list === undefined)
|
|
249
|
+
continue;
|
|
250
|
+
list.push({ node: callee, effect, redirect: null });
|
|
251
|
+
touched.add(list);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const list of touched)
|
|
255
|
+
list.sort((a, b) => a.node.startIndex - b.node.startIndex);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Does this name in a case pattern capture? Value patterns (`a.b`), class
|
|
259
|
+
* names and keyword keys are reads. Every other name is a capture.
|
|
260
|
+
*/
|
|
261
|
+
function isCaseCapture(identifier) {
|
|
262
|
+
const parent = identifier.parent;
|
|
263
|
+
if (parent?.type === 'keyword_pattern')
|
|
264
|
+
return parent.firstNamedChild?.id !== identifier.id;
|
|
265
|
+
if (parent?.type !== 'dotted_name')
|
|
266
|
+
return true;
|
|
267
|
+
if (parent.namedChildCount > 1)
|
|
268
|
+
return false;
|
|
269
|
+
return !(parent.parent?.type === 'class_pattern' && parent.parent.firstNamedChild?.id === parent.id);
|
|
270
|
+
}
|
|
271
|
+
/** Is `node` inside a `match` case pattern? */
|
|
272
|
+
function inCasePattern(node) {
|
|
273
|
+
for (let parent = node.parent; parent !== null; parent = parent.parent) {
|
|
274
|
+
if (parent.type === 'case_pattern')
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The restore a call to `fn` always performs on a module global, or `null`.
|
|
281
|
+
* `fn` must be a plain module-level `def` whose body runs on call (not a
|
|
282
|
+
* generator or coroutine), and every `global` binding of the name in it must
|
|
283
|
+
* be a restore in a simple statement that no earlier `return` can skip.
|
|
284
|
+
*/
|
|
285
|
+
function restoreOnCall(fn, globals) {
|
|
286
|
+
if (fn.type !== 'function_definition' || fn.parent?.type !== 'module')
|
|
287
|
+
return null;
|
|
288
|
+
if (fn.children.some((child) => child.type === 'async'))
|
|
289
|
+
return null;
|
|
290
|
+
const required = fn
|
|
291
|
+
.childForFieldName('parameters')
|
|
292
|
+
?.namedChildren.some((param) => param.type === 'identifier' ||
|
|
293
|
+
(param.type === 'typed_parameter' && param.firstNamedChild?.type === 'identifier'));
|
|
294
|
+
if (required === true)
|
|
295
|
+
return null;
|
|
296
|
+
const body = fn.childForFieldName('body');
|
|
297
|
+
// `globals` is in source order, so the last restore is the one that sticks.
|
|
298
|
+
const first = globals[0];
|
|
299
|
+
const last = globals[globals.length - 1];
|
|
300
|
+
if (body === null || first === undefined || last === undefined)
|
|
301
|
+
return null;
|
|
302
|
+
for (const binding of globals) {
|
|
303
|
+
const statement = statementIn(binding.node, body);
|
|
304
|
+
if (binding.effect === 'shadow' ||
|
|
305
|
+
statement === null ||
|
|
306
|
+
!SIMPLE_STATEMENTS.has(statement.type)) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const escapes = body
|
|
311
|
+
.descendantsOfType(['yield', 'return_statement'])
|
|
312
|
+
.some((node) => scopeOf(node)?.id === fn.id &&
|
|
313
|
+
(node.type === 'yield' || node.startIndex < first.node.startIndex));
|
|
314
|
+
return escapes ? null : last.effect;
|
|
315
|
+
}
|
|
180
316
|
/**
|
|
181
317
|
* The scope that owns names bound at `node`: a function or lambda body, a
|
|
182
318
|
* class body, a comprehension, or the module (`null`). A walrus target skips
|
|
@@ -207,6 +343,10 @@ function statementIn(node, body) {
|
|
|
207
343
|
current = current.parent;
|
|
208
344
|
return current.parent === null ? null : current;
|
|
209
345
|
}
|
|
346
|
+
/** Does `outer` span `node`? */
|
|
347
|
+
function contains(outer, node) {
|
|
348
|
+
return node.startIndex >= outer.startIndex && node.endIndex <= outer.endIndex;
|
|
349
|
+
}
|
|
210
350
|
/** Can `binding` run before `use` in the same scope, including an earlier
|
|
211
351
|
* iteration of an enclosing loop? */
|
|
212
352
|
function mayRunBefore(binding, use, scope) {
|
|
@@ -214,8 +354,7 @@ function mayRunBefore(binding, use, scope) {
|
|
|
214
354
|
return true;
|
|
215
355
|
for (let loop = use.parent; loop !== null && loop.id !== scope?.id; loop = loop.parent) {
|
|
216
356
|
if ((loop.type === 'for_statement' || loop.type === 'while_statement') &&
|
|
217
|
-
binding
|
|
218
|
-
binding.endIndex <= loop.endIndex) {
|
|
357
|
+
contains(loop, binding)) {
|
|
219
358
|
return true;
|
|
220
359
|
}
|
|
221
360
|
}
|
|
@@ -270,6 +409,13 @@ function lookupName(bindings, use) {
|
|
|
270
409
|
const owned = bindings.some((binding) => binding.redirect === null && scopeOf(binding.node)?.id === fn.id);
|
|
271
410
|
if (!owned)
|
|
272
411
|
continue;
|
|
412
|
+
// A nested function can rebind this cell whenever it is called; that
|
|
413
|
+
// order is not modelled, so assume it ran.
|
|
414
|
+
const rebound = bindings.some((binding) => binding.effect === 'shadow' &&
|
|
415
|
+
binding.redirect === 'nonlocal' &&
|
|
416
|
+
contains(fn, binding.node));
|
|
417
|
+
if (rebound)
|
|
418
|
+
return 'shadow';
|
|
273
419
|
// Any binding makes the name local to this function, so the class body
|
|
274
420
|
// reads that cell. An unbound cell raises NameError, not the builtin.
|
|
275
421
|
return namespaceState(bindings, fn, use) === 'builtin' ? 'builtin' : 'shadow';
|
|
@@ -287,16 +433,14 @@ function lookupName(bindings, use) {
|
|
|
287
433
|
// is called; that order is not modelled, so assume it ran.
|
|
288
434
|
if (binding.redirect === 'nonlocal') {
|
|
289
435
|
const outer = functions[functions.length - 1];
|
|
290
|
-
|
|
291
|
-
binding.node.startIndex >= outer.startIndex &&
|
|
292
|
-
binding.node.endIndex <= outer.endIndex;
|
|
293
|
-
if (inside)
|
|
436
|
+
if (outer !== undefined && contains(outer, binding.node))
|
|
294
437
|
return 'shadow';
|
|
295
438
|
}
|
|
296
439
|
if (binding.redirect === 'global') {
|
|
297
440
|
// The function can only be called once the top-level statement that
|
|
298
|
-
// defines it has run. Whether a call happens is unknown, so a
|
|
299
|
-
//
|
|
441
|
+
// defines it has run. Whether a call happens is unknown, so a rebinding
|
|
442
|
+
// one is assumed. A restore counts only at a proven call site (see
|
|
443
|
+
// `addHelperCalls`).
|
|
300
444
|
const top = statementIn(binding.node, binding.node.tree.rootNode);
|
|
301
445
|
if (deferred || (top !== null && mayRunBefore(top, use, null)))
|
|
302
446
|
return 'shadow';
|
|
@@ -807,7 +807,10 @@ import { copyV8CacheIfPresent, tryLoadV8Cache, writeV8CacheFile } from './v8-sid
|
|
|
807
807
|
// comments, honors rebinding of builtin descriptor names visible where the
|
|
808
808
|
// decorator is evaluated, and withholds subtype capacity from descriptor
|
|
809
809
|
// stacks. Warm v120 captures carry the old verdicts.
|
|
810
|
-
|
|
810
|
+
// v122 (#3414): Python decorator identity models restoring helper calls and
|
|
811
|
+
// treats match-pattern captures and nested nonlocal rebinds as shadowing.
|
|
812
|
+
// Warm v121 captures carry the old verdicts.
|
|
813
|
+
const SCHEMA_BUMP = 122;
|
|
811
814
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
812
815
|
try {
|
|
813
816
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED