2626//! - `Any`, `Never`, `Ellipsis` → TSP `BuiltInType`.
2727//! - Solver-internal types → TSP `BuiltInType` with a representative name.
2828//!
29- //! Note: `None` is emitted as a `types. NoneType` `ClassType`, not as a
30- //! `BuiltInType`, because `BuiltInType.name` is restricted to protocol
31- //! sentinel names .
29+ //! Note: `None` is emitted as a `NoneType` `ClassType`, not as a `BuiltInType`,
30+ //! because `BuiltInType.name` is restricted to protocol sentinel names. See
31+ //! [`convert_type_with_resolvers`] for how the version-correct class is sourced .
3232//! All `Type` variants are explicitly handled; no types fall through to a
3333//! generic `SynthesizedType` stub.
3434
@@ -110,16 +110,24 @@ pub type ExportLocationResolver<'a> =
110110
111111/// Convert a pyrefly `Type` to a TSP protocol `Type` using optional
112112/// source-range and module-URI resolvers.
113+ ///
114+ /// `none_type` is the stdlib's authoritative `NoneType` class, used to encode
115+ /// `None`. Passing the real class (rather than re-deriving `NoneType` by name)
116+ /// keeps the declaration version-correct — sourced from `types` on Python 3.10+
117+ /// and `_typeshed` before — and identical to an explicit `types.NoneType`
118+ /// annotation.
113119pub fn convert_type_with_resolvers < ' a > (
114120 ty : & PyreflyType ,
115121 func_range_resolver : Option < & ' a FuncRangeResolver < ' a > > ,
116122 module_path_resolver : Option < & ' a ModulePathResolver < ' a > > ,
117123 export_location_resolver : Option < & ' a ExportLocationResolver < ' a > > ,
124+ none_type : & ' a PyreflyClassType ,
118125) -> TspType {
119126 TypeConverter {
120127 resolve_func_range : func_range_resolver,
121128 resolve_module_path : module_path_resolver,
122129 resolve_export : export_location_resolver,
130+ none_type,
123131 }
124132 . convert ( ty)
125133}
@@ -131,14 +139,43 @@ pub fn convert_type_with_resolvers<'a>(
131139/// locations are needed.
132140#[ cfg( test) ]
133141pub fn convert_type ( ty : & PyreflyType ) -> TspType {
134- convert_type_with_resolvers ( ty, None , None , None )
142+ convert_type_with_resolvers ( ty, None , None , None , & test_none_type ( ) )
143+ }
144+
145+ /// Build a stand-in for `Stdlib::none_type()` used by the resolver-free tests,
146+ /// which have no real stdlib. Mirrors the real class closely enough to exercise
147+ /// the production `None` path: a top-level `NoneType` class in bundled `types`.
148+ #[ cfg( test) ]
149+ fn test_none_type ( ) -> PyreflyClassType {
150+ use pyrefly_python:: module:: Module ;
151+ use pyrefly_python:: nesting_context:: NestingContext ;
152+ use pyrefly_types:: class:: ClassDefIndex ;
153+ use pyrefly_types:: types:: TArgs ;
154+ use ruff_python_ast:: Identifier ;
155+
156+ let module = Module :: new (
157+ ModuleName :: types ( ) ,
158+ ModulePath :: bundled_typeshed ( PathBuf :: from ( "types.pyi" ) ) ,
159+ Arc :: new ( String :: new ( ) ) ,
160+ ) ;
161+ let class = Class :: new (
162+ ClassDefIndex ( 0 ) ,
163+ Identifier :: new ( Name :: new ( "NoneType" ) , TextRange :: default ( ) ) ,
164+ NestingContext :: toplevel ( ) ,
165+ module,
166+ None ,
167+ ) ;
168+ PyreflyClassType :: new ( class, TArgs :: default ( ) )
135169}
136170
137171/// Holds an optional range resolver and drives recursive type conversion.
138172struct TypeConverter < ' a > {
139173 resolve_func_range : Option < & ' a FuncRangeResolver < ' a > > ,
140174 resolve_module_path : Option < & ' a ModulePathResolver < ' a > > ,
141175 resolve_export : Option < & ' a ExportLocationResolver < ' a > > ,
176+ /// The stdlib's authoritative `NoneType` class, used to encode `None`; see
177+ /// [`convert_type_with_resolvers`].
178+ none_type : & ' a PyreflyClassType ,
142179}
143180
144181impl TypeConverter < ' _ > {
@@ -148,7 +185,8 @@ impl TypeConverter<'_> {
148185 // --- Built-in special types ---
149186 PyreflyType :: Any ( _) => builtin ( "any" ) ,
150187 PyreflyType :: Never ( _) => builtin ( "never" ) ,
151- PyreflyType :: None => self . types_class ( "NoneType" , TypeFlags :: INSTANCE ) ,
188+ // `None` → the stdlib's real `NoneType` class (see `none_type`).
189+ PyreflyType :: None => self . convert_class_type ( self . none_type , TypeFlags :: INSTANCE ) ,
152190 PyreflyType :: Ellipsis => builtin ( "ellipsis" ) ,
153191
154192 // --- Class instances (int, str, list[int], user-defined classes, etc.) ---
@@ -662,19 +700,6 @@ impl TypeConverter<'_> {
662700 } )
663701 }
664702
665- /// Build a TSP `ClassType` whose declaration points at `types.<name>`.
666- fn types_class ( & self , name : & str , flags : TypeFlags ) -> TspType {
667- TspType :: Class ( TspClassType {
668- declaration : Declaration :: Regular ( self . types_class_declaration ( name) ) ,
669- flags,
670- id : next_id ( ) ,
671- kind : TypeKind :: Class ,
672- literal_value : None ,
673- type_alias_info : None ,
674- type_args : None ,
675- } )
676- }
677-
678703 /// Build a class declaration for `typing.<name>`. Resolves the real source
679704 /// range via the export-location resolver; falls back to a zero range
680705 /// pointing at bundled `typing.pyi` when unavailable.
@@ -697,28 +722,6 @@ impl TypeConverter<'_> {
697722 make_typing_class_declaration ( name)
698723 }
699724
700- /// Build a class declaration for `types.<name>`. Resolves the real source
701- /// range via the export-location resolver; falls back to a zero range
702- /// pointing at bundled `types.pyi` when unavailable.
703- fn types_class_declaration ( & self , name : & str ) -> RegularDeclaration {
704- let symbol = Name :: new ( name) ;
705- if let Some ( ( module_path, lsp_range) ) = self
706- . resolve_export
707- . and_then ( |resolve| resolve ( ModuleName :: types ( ) , & symbol) )
708- {
709- return RegularDeclaration {
710- kind : DeclarationKind :: Regular ,
711- category : DeclarationCategory :: Class ,
712- name : Some ( name. to_owned ( ) ) ,
713- node : Node {
714- range : lsp_range_to_tsp ( lsp_range) ,
715- uri : path_to_uri ( & module_path) ,
716- } ,
717- } ;
718- }
719- make_types_class_declaration ( name)
720- }
721-
722725 /// Convert a pyrefly `Overload` to a TSP `OverloadedType`.
723726 fn convert_overload_to_tsp (
724727 & self ,
@@ -875,21 +878,6 @@ fn make_typing_class_declaration(name: &str) -> RegularDeclaration {
875878 }
876879}
877880
878- /// Build a declaration for a class in `types.pyi`.
879- fn make_types_class_declaration ( name : & str ) -> RegularDeclaration {
880- let module_path =
881- pyrefly_python:: module_path:: ModulePath :: bundled_typeshed ( PathBuf :: from ( "types.pyi" ) ) ;
882- RegularDeclaration {
883- kind : DeclarationKind :: Regular ,
884- category : DeclarationCategory :: Class ,
885- name : Some ( name. to_owned ( ) ) ,
886- node : Node {
887- range : zero_range ( ) ,
888- uri : path_to_uri ( & module_path) ,
889- } ,
890- }
891- }
892-
893881/// Build a TSP `TypeVar` with a synthesized declaration. Used for
894882/// solver-internal TypeVar-like placeholders (Quantified, Args, Kwargs,
895883/// ElementOfTypeVarTuple) where there is no real source location.
@@ -1422,7 +1410,13 @@ mod tests {
14221410 None
14231411 }
14241412 } ;
1425- let tsp = convert_type_with_resolvers ( & ty, None , Some ( & module_path_resolver) , None ) ;
1413+ let tsp = convert_type_with_resolvers (
1414+ & ty,
1415+ None ,
1416+ Some ( & module_path_resolver) ,
1417+ None ,
1418+ & test_none_type ( ) ,
1419+ ) ;
14261420 match tsp {
14271421 TspType :: Module ( m) => {
14281422 assert_eq ! ( m. module_name, "pkg" ) ;
@@ -1499,7 +1493,7 @@ mod tests {
14991493 range,
15001494 ) )
15011495 } ;
1502- match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) ) {
1496+ match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) , & test_none_type ( ) ) {
15031497 TspType :: Class ( c) => {
15041498 let Declaration :: Regular ( decl) = c. declaration else {
15051499 panic ! ( "expected RegularDeclaration" ) ;
@@ -1618,7 +1612,7 @@ mod tests {
16181612 ) )
16191613 } ;
16201614 let ty = PyreflyType :: TypeAlias ( Box :: new ( TypeAliasData :: Ref ( make_ref ( ) ) ) ) ;
1621- match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) ) {
1615+ match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) , & test_none_type ( ) ) {
16221616 TspType :: Class ( c) => {
16231617 let Declaration :: Regular ( decl) = c. declaration else {
16241618 panic ! ( "expected RegularDeclaration" ) ;
@@ -1679,7 +1673,7 @@ mod tests {
16791673 range,
16801674 ) )
16811675 } ;
1682- match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) ) {
1676+ match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) , & test_none_type ( ) ) {
16831677 TspType :: Var ( v) => {
16841678 let Declaration :: Regular ( decl) = v. declaration else {
16851679 panic ! ( "expected RegularDeclaration" ) ;
@@ -1710,7 +1704,7 @@ mod tests {
17101704 lsp_types:: Range :: default ( ) ,
17111705 ) )
17121706 } ;
1713- match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) ) {
1707+ match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) , & test_none_type ( ) ) {
17141708 TspType :: Var ( v) => {
17151709 let Declaration :: Regular ( decl) = v. declaration else {
17161710 panic ! ( "expected RegularDeclaration" ) ;
@@ -1857,22 +1851,19 @@ mod tests {
18571851 character : 12 ,
18581852 } ,
18591853 } ;
1854+ // The `None` return converts through the stdlib `NoneType` class, not
1855+ // the export resolver, so the only lookup here is `typing.overload`;
1856+ // any other lookup is a regression.
18601857 let resolver = |module : ModuleName , name : & Name | {
18611858 if module == ModuleName :: typing ( ) && name. as_str ( ) == "overload" {
18621859 return Some ( (
18631860 ModulePath :: filesystem ( PathBuf :: from ( "/typeshed/typing.pyi" ) ) ,
18641861 range,
18651862 ) ) ;
18661863 }
1867- if module == ModuleName :: types ( ) && name. as_str ( ) == "NoneType" {
1868- return Some ( (
1869- ModulePath :: filesystem ( PathBuf :: from ( "/typeshed/types.pyi" ) ) ,
1870- range,
1871- ) ) ;
1872- }
18731864 panic ! ( "unexpected export lookup for {module}.{name}" ) ;
18741865 } ;
1875- match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) ) {
1866+ match convert_type_with_resolvers ( & ty, None , None , Some ( & resolver) , & test_none_type ( ) ) {
18761867 TspType :: Function ( f) => {
18771868 let Declaration :: Regular ( decl) = f. declaration else {
18781869 panic ! ( "expected RegularDeclaration" ) ;
0 commit comments