mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 03:49:59 +08:00
fix(extraction): a function in an initializer is referenced once, by what the initializer declares (#2743)
PRs #693 and #1802 walk a declaration's initializer under the symbol it declares, so `const routes = { home }` references `home` from `routes`. The dispatcher's declaration-wide function-as-value scan then took the same values again for the enclosing file (or class). Flush-time dedup keys on the source, so both stayed: a second reference to the same function from the same spot. Each initializer walk now records what it walked, and the scan skips it. PR #2738 added that mechanism for component wrappers, and C#, Scala and Dart already had their own. The newly covered paths: - TS/JS module-level constants and variables, owned object literals included (#2300), and TS/JS class properties; - Python assignments, Go var/const specs, Rust const/static items; - Java fields, and Kotlin properties (walkInitializer takes the property's id, so their walk is unchanged). Mirrored in the kernel: tsjs, python, go, rustlang, java and kotlin. References no initializer walk takes are unchanged: a module-level call argument `register(fn)`, C-family tables, Rust's `static CB: fn() = handler` (the declaration's own value, which the scan still reads). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ae0fc631e0
commit
51be28d15b
@@ -31,6 +31,7 @@ public class TortureService extends BaseService implements Runnable, AutoCloseab
|
||||
}
|
||||
};
|
||||
private final Runnable fieldMethodRef = TortureService::compute;
|
||||
private static final List<Runnable> FIELD_TABLE = List.of(TortureService::compute);
|
||||
|
||||
/** Ctor javadoc. */
|
||||
public TortureService(List<String> names) {
|
||||
|
||||
@@ -14,6 +14,7 @@ const arrow = (x) => legacyHelper(x, 1);
|
||||
|
||||
class Widget extends EventEmitter {
|
||||
static registry = new Map();
|
||||
static hooks = { tick: legacyHelper };
|
||||
#privateField = 1;
|
||||
label = 'w';
|
||||
onTick = () => {
|
||||
@@ -78,6 +79,7 @@ export default {
|
||||
// leaked to the FILE node; a non-exported object literal was skipped outright.
|
||||
const eagerConfig = loadConfig();
|
||||
const handlerMap = { onSave: () => persist(eagerConfig), onLoad: loadConfig() };
|
||||
const registry = { legacyHelper, run: arrow, nested: [legacyHelper] };
|
||||
const lazyList = [() => persist(eagerConfig)];
|
||||
// --- CommonJS export assignments (#1675) -----------------------------------
|
||||
exports.getItems = async (req, res) => { res.json(await findItems()); };
|
||||
|
||||
@@ -52,6 +52,7 @@ val withGetter: Int
|
||||
get() = 42
|
||||
val initLambda: () -> Unit = { caller() }
|
||||
val initSam = Runnable { caller() }
|
||||
val handlerList = listOf(::caller)
|
||||
val initObject = object : Runnable {
|
||||
override fun run() {
|
||||
caller()
|
||||
@@ -60,6 +61,7 @@ val initObject = object : Runnable {
|
||||
|
||||
class WidgetK(val size: Int, private var name: String = defaultName()) {
|
||||
val area: Int = size * size
|
||||
val hooks = listOf(::caller)
|
||||
var label: String? = null
|
||||
val computed: Int
|
||||
get() = size * 2
|
||||
|
||||
@@ -305,6 +305,8 @@ routes![top_level_h];
|
||||
const INIT_CONST: usize = compute_len();
|
||||
static INIT_LAZY: Lazy<Cfg> = Lazy::new(|| build_cfg());
|
||||
static INIT_ALIAS: fn() = free_fn;
|
||||
// A table the walk above takes for its symbol, and the scan no longer for the file.
|
||||
static INIT_TABLE: &[fn()] = &[handler, handler2];
|
||||
|
||||
pub union Reg {
|
||||
pub raw: u32,
|
||||
|
||||
@@ -23,6 +23,7 @@ var oldVar = 'x';
|
||||
@scoped.Registry<Config>('name')
|
||||
export abstract class BaseService extends EventTarget implements Disposable, Serializable {
|
||||
static instances = 0;
|
||||
static handlers = { refresh: helperFn, table: CONFIG_TABLE };
|
||||
private readonly cache: Map<string, Config> = new Map();
|
||||
public fonts: FontConfig;
|
||||
count = 0;
|
||||
@@ -189,6 +190,7 @@ export const actionsMap = {
|
||||
update(input: UpdateInput) {
|
||||
return persistExisting(input);
|
||||
},
|
||||
fallback: helperFn,
|
||||
};
|
||||
|
||||
// Zustand-style store through middleware wrappers.
|
||||
|
||||
@@ -779,12 +779,11 @@ describe('Function-as-value capture (#756)', () => {
|
||||
|
||||
// The DRF wiring: get_serializer_class → the imported serializer class,
|
||||
// via `return` — the issue's headline gap. The module-level registry
|
||||
// dict rides BOTH the assigned name (the initializer walk, #693) and the
|
||||
// file node (the dispatcher's own scan, which runs either way).
|
||||
// dict rides the assigned name (the initializer walk, #693), and not the
|
||||
// file node as well: the dispatcher's scan skips what that walk took.
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'OrgSerializerFull'))).toEqual([
|
||||
'SERIALIZER_REGISTRY',
|
||||
'get_serializer_class',
|
||||
'views.py',
|
||||
]);
|
||||
// Second branch return + a module-level call argument.
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'OrgSerializerBrief'))).toEqual([
|
||||
@@ -1581,4 +1580,72 @@ func Unknown(obj interface{}) { Submit(obj.Fetch) }
|
||||
cg.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// An initializer walked under the symbol it declares (#693) takes its
|
||||
// function values for that symbol. The declaration-wide scan took each one
|
||||
// again for the enclosing file or class: a second reference to the same
|
||||
// function from the same spot (TS/JS constants and class fields, Python,
|
||||
// Go, Rust, Java fields, Kotlin properties). Each function here is
|
||||
// referenced only from an initializer, so a second source shows.
|
||||
it('INITIALIZER WALKS: a function in an initializer is referenced once, by what it declares', async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-fnref-init-'));
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'app.ts'),
|
||||
[
|
||||
'export function tsHome() {}',
|
||||
'export function tsAbout() {}',
|
||||
'export function tsClick() {}',
|
||||
'export function tsBoot() {}',
|
||||
'function register(fn: () => void) {}',
|
||||
'const routes = { tsHome, about: tsAbout, list: [tsHome] };',
|
||||
'const api = { load() {}, tsAbout };',
|
||||
'export class Widget {',
|
||||
' static handlers = { click: tsClick };',
|
||||
'}',
|
||||
'register(tsBoot);',
|
||||
].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'views.py'),
|
||||
['def py_home():', ' pass', '', 'HANDLERS = {"home": py_home}', 'ROUTES = [py_home]'].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'handlers.go'),
|
||||
['package main', '', 'func goHome() {}', '', 'var handlers = map[string]func(){"home": goHome}'].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'lib.rs'),
|
||||
['fn rust_home() {}', 'static HANDLERS: &[fn()] = &[rust_home];'].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'Registry.java'),
|
||||
[
|
||||
'class Registry {',
|
||||
' static void javaHome() {}',
|
||||
' static final java.util.List<Runnable> TABLE = java.util.List.of(Registry::javaHome);',
|
||||
'}',
|
||||
].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'handlers.kt'),
|
||||
['fun ktHome() {}', 'val ktHandlers = listOf(::ktHome)', 'class KtWidget {', ' val hooks = listOf(::ktHome)', '}'].join('\n')
|
||||
);
|
||||
|
||||
const cg = CodeGraph.initSync(tmpDir);
|
||||
try {
|
||||
await cg.indexAll();
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsHome'))).toEqual(['routes']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsAbout'))).toEqual(['api', 'routes']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsClick'))).toEqual(['handlers']);
|
||||
// A call argument at module scope is the file's, as before.
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsBoot'))).toEqual(['app.ts']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'py_home'))).toEqual(['HANDLERS', 'ROUTES']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'goHome'))).toEqual(['handlers']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'rust_home'))).toEqual(['HANDLERS']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'javaHome'))).toEqual(['TABLE']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'ktHome'))).toEqual(['hooks', 'ktHandlers']);
|
||||
} finally {
|
||||
cg.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user