mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
fix(resolution): explicit dotted aliases, one-line __main__ guards, scoped vars()
Review follow-up. - `import pkg.settings as settings` binds `settings` even though it equals the last segment; the import mapping cannot tell it from a plain `import pkg.settings`, so the source line decides. - `if __name__ == "__main__": stmt` on one line is script code, and a `globals()` write inside a `__main__` block no longer counts. - `vars()` is the module dict only at module scope; inside a function it is the locals, so `return vars()` there no longer makes the type unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CQubRS1bXvaoApgPyLf66
This commit is contained in:
@@ -1439,6 +1439,16 @@ def init():
|
||||
namespace_import_binds_root: [{ 'pkg/__init__.py': '', 'pkg/settings.py': header,
|
||||
'c.py': 'import pkg.settings\nfrom decoy import Decoy\n\nsettings = Bag()\nsettings.conn = Decoy()\n',
|
||||
'd.py': 'from pkg import settings\n\ndef cb(pool):\n pool.submit(settings.conn.fetch)\n' }, ['cb -> Store::fetch']],
|
||||
explicit_alias_equal_to_leaf: [{ 'pkg/__init__.py': '', 'pkg/settings.py': header,
|
||||
'reset.py': 'import pkg.settings as settings\nfrom decoy import Decoy\n\ndef reset():\n settings.conn = Decoy()\n',
|
||||
'd.py': 'from pkg import settings\n\ndef cb(pool):\n pool.submit(settings.conn.fetch)\n' }, []],
|
||||
one_line_main_write: [{ 'settings.py': header,
|
||||
'run.py': 'import settings\nfrom decoy import Decoy\n\nif __name__ == "__main__": settings.conn = Decoy()\n',
|
||||
'c.py': 'import settings\n\ndef cb(pool):\n pool.submit(settings.conn.fetch)\n' }, ['cb -> Store::fetch']],
|
||||
globals_in_main_block: [{ 'm.py': header + 'if __name__ == "__main__":\n globals()["conn"] = Decoy()\n\ndef cb(pool):\n pool.submit(conn.fetch)\n' },
|
||||
['cb -> Store::fetch']],
|
||||
vars_in_function_is_locals: [{ 'm.py': header + 'def dump():\n return vars()\n\ndef cb(pool):\n pool.submit(conn.fetch)\n' },
|
||||
['cb -> Store::fetch']],
|
||||
example_write_is_production: [{ 'settings.py': header,
|
||||
'examples/wire.py': 'import settings\nfrom decoy import Decoy\n\ndef wire():\n settings.conn = Decoy()\n',
|
||||
'c.py': 'import settings\n\ndef cb(pool):\n pool.submit(settings.conn.fetch)\n' }, []],
|
||||
|
||||
@@ -684,7 +684,10 @@ function pythonModuleAliases(filePath: string, moduleFile: string, context: Reso
|
||||
: /^\.+$/.test(m.source) ? `${m.source}${m.exportedName}` : `${m.source}.${m.exportedName}`;
|
||||
const files = pythonModuleFiles(dotted, filePath, context);
|
||||
if (!files.includes(moduleFile)) continue;
|
||||
const plainDotted = m.isNamespace && m.source.includes('.') && m.localName === m.source.split('.').pop();
|
||||
// The mapping cannot tell `import a.b` from `import a.b as b`; the source line can.
|
||||
const explicit = new RegExp(`\\bimport\\s[^\\n]*\\b${m.source.replace(/\./g, '\\.')}\\s+as\\s+${m.localName}\\b`);
|
||||
const plainDotted = m.isNamespace && m.source.includes('.') && m.localName === m.source.split('.').pop() &&
|
||||
!pythonMemberLines(filePath, context).some(line => explicit.test(line));
|
||||
(files.length === 1 ? aliases : ambiguous).add(plainDotted ? m.source : m.localName);
|
||||
}
|
||||
return { aliases: [...aliases], ambiguous: [...ambiguous] };
|
||||
@@ -697,7 +700,9 @@ function pythonMainBlockLines(filePath: string, context: ResolutionContext): Set
|
||||
const lines = pythonMemberLines(filePath, context);
|
||||
const inside = new Set<number>();
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (!PYTHON_MAIN_GUARD.test(lines[i]!)) continue;
|
||||
const guard = lines[i]!.match(PYTHON_MAIN_GUARD);
|
||||
if (!guard) continue;
|
||||
if (lines[i]!.slice(guard[0].length).trim()) inside.add(i); // `if __name__ == "__main__": stmt`
|
||||
for (let j = i + 1; j < lines.length && !/^\S/.test(lines[j]!); j++) inside.add(j);
|
||||
}
|
||||
return inside;
|
||||
@@ -773,12 +778,17 @@ function pythonExternalWrites(global: Node, context: ResolutionContext): { write
|
||||
function pythonDynamicGlobalWrite(name: string, filePath: string, context: ResolutionContext): boolean {
|
||||
const lines = pythonMemberLines(filePath, context);
|
||||
const statements = pythonStatementStarts(filePath, context);
|
||||
const namespace = /\bglobals\(\s*\)|\bvars\(\s*\)|\bsys\.modules\s*\[\s*__name__\s*\]/g;
|
||||
const script = pythonMainBlockLines(filePath, context);
|
||||
// `vars()` is the module dict only at module scope; inside a function it is the locals.
|
||||
const moduleLines = new Set(pythonOwnLines(null, filePath, context));
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (!statements[i]) continue;
|
||||
if (!statements[i] || script.has(i)) continue;
|
||||
let statement = lines[i]!;
|
||||
for (let j = i + 1; j < lines.length && !statements[j]; j++) statement += '\n' + lines[j];
|
||||
const code = blankPythonStrings(statement);
|
||||
const namespace = moduleLines.has(i)
|
||||
? /\bglobals\(\s*\)|\bvars\(\s*\)|\bsys\.modules\s*\[\s*__name__\s*\]/g
|
||||
: /\bglobals\(\s*\)|\bsys\.modules\s*\[\s*__name__\s*\]/g;
|
||||
const uses = code.match(namespace)?.length ?? 0;
|
||||
if (!uses) continue;
|
||||
const targets = pythonAssignment(statement)?.targets ?? [];
|
||||
|
||||
Reference in New Issue
Block a user