Skip to content

Python: enumerate module-level functions and constants in DependencyTypes - #8375

Merged
jkschneider merged 1 commit into
mainfrom
verify-dependencytypes-misses-top-level-functions
Aug 4, 2026
Merged

Python: enumerate module-level functions and constants in DependencyTypes#8375
jkschneider merged 1 commit into
mainfrom
verify-dependencytypes-misses-top-level-functions

Conversation

@knutwannheden

@knutwannheden knutwannheden commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • The DependencyTypes RPC (Add a DependencyTypes RPC to enumerate a dependency's public API #8360) enumerates a pip dependency's (or stdlib module's) public API so the Moderne CLI can serialize it into a shared per-coordinate type table. The enumeration harvested only classLiteral descriptors, so module-level functions (click.echo) and constants (os.sep) never reached the table. Parse-time attribution declares such references under a JavaType.Class named after the module (the declaring type of click.echo is a class with FQN click), but the enumeration never defined that class — so those references could never be resolved against the dependency's table and stayed shallow in every consumer, and the module-level half of a dependency's API was simply missing from the shared table.

Examples

For a distribution containing:

# toplib/__init__.py
GREETING = "hello"
TIMEOUT: float

def shout(text: str) -> str:
    return text.upper()

class Thing:
    def run(self) -> None: ...

the enumeration now defines toplib.Thing (as before) plus a toplib class whose methods include shout and whose members include GREETING and TIMEOUT. Re-exports are attributed to the re-exporting package: with click/__init__.py doing from click.utils import echo, the click module type carries echo as a method (under its binding name, so from x import echo as shout binds shout), while a re-exported class keeps its defining FQN.

Summary

  • PythonTypeMapping.module_type(fqn) synthesizes a JavaType.Class for a module: public top-level functions as methods, public top-level constants (including annotation-only, stub-style declarations) as members, and re-exported bindings under their binding names. The public surface is __all__ when declared, else the non-underscore names. ty emits no descriptor for a file's own module and its registry cannot distinguish module-level functions from methods (both carry only moduleName), so the harvest walks the file's top-level AST and resolves each binding through the node index.
  • _enumerate_artifact emits that module type per file; the first file defines a module's type, which the stub-first ordering makes the .pyi when one exists.
  • _FUNCTION_KINDS replaces the six copies of the function-kind tuple, and _create_method_from_descriptor takes an optional name override for aliased re-exports.

Test plan

  • New test: module-level function, constants (assigned and annotation-only), and class all enumerated; the class stays off the module type
  • New test: re-exports surface on the package module type under their binding names (including aliases); re-exported classes keep their defining FQN; submodules get their own module type
  • New test: underscore convention without __all__; __all__ as the public surface when declared
  • pytest tests/rpc/test_dependency_types.py — 26 passed
  • pytest tests/python/test_type_attribution.py — 151 passed, 4 skipped (type-attribution regression check for the shared type_mapping.py changes)

…ypes

Follow-up to #8360: the enumeration kept only classLiteral descriptors, so a
dependency's module-level functions (click.echo) and constants (os.sep) never
reached the shared type table. Synthesize a per-module JavaType.Class — public
top-level functions as methods, constants as members, re-exports under their
binding names — matching how attribution declares such references.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Aug 4, 2026
@jkschneider
jkschneider merged commit deb3ff0 into main Aug 4, 2026
1 check passed
@jkschneider
jkschneider deleted the verify-dependencytypes-misses-top-level-functions branch August 4, 2026 13:57
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants