yetuge d01538a54c
fix(reflection): reject malformed variable paths before diagnosing them (#7016)
* fix(reflection): reject malformed variable paths before diagnosing them

resolve_variable splits on the last colon and assumed both halves were
usable. Two realistic typos fell through into misleading diagnoses:

- "json:" reached getattr(module, "") and reported
  "Module json does not define a  attribute/class" -- a blank name in the
  middle of the sentence.
- "json:loads:extra" left the extra colon inside the module part, so the
  failed import was reported as Missing dependency and the user was told to
  run uv add json:loads, an instruction that cannot work.

Both are path mistakes, so they now get the same format guidance as a path
with no colon at all. Every provider, extension and model class path in the
repo flows through here, so the message is what an operator sees first.

Tests cover the two typos and assert the missing-dependency hint is not
offered for them.

* fix(reflection): reject relative module paths too, and pin the empty-module half

Review follow-ups on the malformed-path check:

- A leading-dot path (.foo:bar) is a relative import, and import_module
  raises TypeError rather than ImportError for it. Every loader built on this
  function catches ImportError, so the call crashed instead of reporting the
  path. These paths are absolute by construction, so reject the shape.
- ":attr" was already covered by the new condition but had no test, which
  left the empty-module half unpinned. Added one.

Both were mutation-checked: dropping each condition turns exactly its own
test red.

* fix(reflection): reject whitespace and trailing dots in variable paths too

Review follow-up on the shapes that stay inside the ImportError contract but
land in the missing-dependency branch:

- "json.:attr" advertises a distribution named `json.`
- "json :loads" and " json:attr" advertise `uv add json ` / `uv add  json`,
  where the space that IS the typo is invisible in the rendered hint
- "json:attr\n" reaches getattr with a name the caller never wrote

A dotted module path has no trailing dot and neither half carries
whitespace, so these get the same format guidance as the other four shapes.
Whitespace is checked inside both halves rather than at the ends, since
"json :loads" hides the typo in the middle.

Each new condition half is mutation-checked: removing the trailing-dot test
fails only the trailing-dot case, and removing both whitespace tests fails
exactly the two whitespace cases.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-22 08:05:17 +08:00
..