2 Commits

Author SHA1 Message Date
Martin Vogel 2e042dab0b feat(py_lsp): Round 1 parity push (parent-modules, cast, Self, fwd-refs)
Parity push #1 — six gap fixes on top of the 11-phase plan:

- Parent-module bindings: `import a.b.c` now binds `a`, `a.b`, `a.b.c`
  via py_bind_dotted_prefixes. Walks the dotted prefix chain on every
  import, regardless of import_is_from_style. Plus an attribute-access
  path on a MODULE that detects submodules: when the registry has any
  function whose qn starts with `<mod>.<attr>.`, evaluating `mod.attr`
  yields MODULE("<mod>.<attr>"). This makes `os.path.join(...)` and
  similar resolve correctly.

- typing.cast(T, x) returns NAMED(T) (re-evaluating T as an annotation,
  so generic subscripts and forward references both resolve). Detects
  bare `cast(...)` and qualified `typing.cast(...)` forms.

- typing.assert_type(x, T) is a no-op at runtime; we type the result
  as type-of(x).

- Forward references as quoted strings: `def f(x: "Foo")` strips quotes
  and re-resolves the inner annotation. Also handles double-quoted and
  single-quoted forms.

- Self return type substitution: methods declared returning `Self` /
  `typing.Self` / `typing_extensions.Self` substitute to NAMED(receiver)
  at call resolution time. Enables fluent / builder pattern chaining
  to keep resolving methods after each step.

- Generic subscript stripping: annotations like `list[Foo]` strip to
  `list` for v1; `Optional[Foo]` likewise. Container element-type
  substitution still deferred (Phase 8+).

Generator additions:
- Allowlist now includes urllib, http, concurrent (top stdlib usage).
- `from X import *` re-export following at generation time. Modules
  like os.path (a star-import shim of posixpath / ntpath / genericpath)
  now have all forwarded definitions registered under their own QN.
  Iterates to a fixed point with an 8-step ceiling. typeshed tree
  expansion is bounded — only modules transitively reachable from the
  allowlist are pulled in.

Stats: 137 modules, 904 classes (2797 methods), 886 free functions
(was 114 / 753 / 794). Generated file grows from 20K to ~22K lines.

6 new test_py_lsp.c cases. All 2874 prior tests stay green.
2026-05-09 01:39:28 +02:00
Martin Vogel fa6bfd125f feat(py_lsp): stdlib registry from typeshed + generator
Phase 10 of Python LSP integration. Adds:

- scripts/gen-py-stdlib.py: walks a typeshed/stdlib checkout, parses
  each .pyi via Python's stdlib `ast` module, and emits one C source
  file populating cbm_python_stdlib_register. v1 simplifications:
    - overload stacks collapse to first signature
    - ParamSpec / TypeVarTuple / Concatenate skipped
    - version guards (if sys.version_info >= ...) flattened to union
      of branches
    - per-symbol min/max version guards not yet emitted (v1.1 follow-up)
  Module allowlist matches PYTHON_LSP_PLAN.md Phase 10 list — top
  stdlib modules by usage in indexed Python projects, skipping
  tkinter / turtle / curses / xml / email for size.

- internal/cbm/lsp/generated/python_stdlib_data.c (auto-generated,
  20,021 lines): 114 modules, 753 classes (2,385 methods), 794 free
  functions registered. Source pinned to typeshed commit
  a7912d521e16ff63caf7a8b64b9072542be36777 (recorded in header).
  Compiles cleanly under ASan + UBSan.

- lsp_all.c includes the generated file. The CBM_PYTHON_STDLIB_GENERATED
  macro disables py_lsp.c's no-op stub so the real registration runs.

4 new test_py_lsp.c cases verify resolution against the registry:
os.getcwd, collections.defaultdict constructor, pathlib.Path.exists
method via typed parameter, logging.getLogger.

Known v1 limitation captured in test comments: `import os.path` only
binds the leaf `path` in scope, so `os.path.join` style chained
attribute access on the parent module name doesn't yet resolve.
Phase 10.5 will stamp parent-module bindings.

All 2869 prior tests stay green; 4 new stdlib tests pass.
2026-05-09 00:39:21 +02:00