Python: enumerate module-level functions and constants in DependencyTypes - #8375
Merged
jkschneider merged 1 commit intoAug 4, 2026
Merged
Conversation
…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.
jkschneider
deleted the
verify-dependencytypes-misses-top-level-functions
branch
August 4, 2026 13:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
DependencyTypesRPC (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 onlyclassLiteraldescriptors, so module-level functions (click.echo) and constants (os.sep) never reached the table. Parse-time attribution declares such references under aJavaType.Classnamed after the module (the declaring type ofclick.echois a class with FQNclick), 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:
the enumeration now defines
toplib.Thing(as before) plus atoplibclass whose methods includeshoutand whose members includeGREETINGandTIMEOUT. Re-exports are attributed to the re-exporting package: withclick/__init__.pydoingfrom click.utils import echo, theclickmodule type carriesechoas a method (under its binding name, sofrom x import echo as shoutbindsshout), while a re-exported class keeps its defining FQN.Summary
PythonTypeMapping.module_type(fqn)synthesizes aJavaType.Classfor 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 onlymoduleName), so the harvest walks the file's top-level AST and resolves each binding through the node index._enumerate_artifactemits that module type per file; the first file defines a module's type, which the stub-first ordering makes the.pyiwhen one exists._FUNCTION_KINDSreplaces the six copies of the function-kind tuple, and_create_method_from_descriptortakes an optionalnameoverride for aliased re-exports.Test plan
__all__;__all__as the public surface when declaredpytest tests/rpc/test_dependency_types.py— 26 passedpytest tests/python/test_type_attribution.py— 151 passed, 4 skipped (type-attribution regression check for the sharedtype_mapping.pychanges)