fix(resolution): explicit-alias check matches the exact module, outside strings

Review follow-up: `import other.pkg.settings as settings` or a string
`"import pkg.settings as settings"` no longer marks a plain
`import pkg.settings` as explicitly aliased, which attached a write to the
wrong module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CQubRS1bXvaoApgPyLf66
This commit is contained in:
Josh66
2026-09-28 13:27:12 +00:00
parent 0a5fb69797
commit a5569a07ea
2 changed files with 13 additions and 2 deletions
+10
View File
@@ -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']],
// `other.pkg.settings as settings` is not an explicit alias of `pkg.settings`: the
// write goes to the other module, so pkg's global keeps its type (the consumer's
// relative import names pkg/settings.py exactly).
alias_of_longer_module_is_not_explicit: [{ 'pkg/__init__.py': '', 'pkg/settings.py': header,
'other/__init__.py': '', 'other/pkg/__init__.py': '', 'other/pkg/settings.py': 'conn = None\n',
'reset.py': 'import pkg.settings\nimport other.pkg.settings as settings\nfrom decoy import Decoy\n\ndef reset():\n settings.conn = Decoy()\n',
'pkg/d.py': 'from . import settings\n\ndef cb(pool):\n pool.submit(settings.conn.fetch)\n' }, ['cb -> Store::fetch']],
alias_in_string_is_not_explicit: [{ 'pkg/__init__.py': '', 'pkg/settings.py': 'conn = None\n',
'reset.py': 'import pkg.settings\nfrom decoy import Decoy\nnote = "import pkg.settings as settings"\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' }, []],
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' }, []],
+3 -2
View File
@@ -685,9 +685,10 @@ function pythonModuleAliases(filePath: string, moduleFile: string, context: Reso
const files = pythonModuleFiles(dotted, filePath, context);
if (!files.includes(moduleFile)) continue;
// 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`);
// Exactly this module (not `other.a.b`), outside string literals.
const explicit = new RegExp(`\\bimport\\s[^\\n]*(?<![\\w.])${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));
!pythonMemberLines(filePath, context).some(line => explicit.test(blankPythonStrings(line)));
(files.length === 1 ? aliases : ambiguous).add(plainDotted ? m.source : m.localName);
}
return { aliases: [...aliases], ambiguous: [...ambiguous] };