@@ -15,6 +15,23 @@ use crate::report::cinderx::write_results;
1515use crate :: state:: require:: Require ;
1616use crate :: test:: util:: TestEnv ;
1717
18+ /// Minimal `__static__` stub for CinderX primitive type tests.
19+ /// In production, the real `__static__` package is provided by CinderX.
20+ const STATIC_MODULE_STUB : & str = r#"
21+ class int8(int): pass
22+ class int16(int): pass
23+ class int32(int): pass
24+ class int64(int): pass
25+ class uint8(int): pass
26+ class uint16(int): pass
27+ class uint32(int): pass
28+ class uint64(int): pass
29+ class cbool(int): pass
30+ class char(int): pass
31+ class double(float): pass
32+ class single(float): pass
33+ "# ;
34+
1835/// Create a type-checked state from a single module's Python source.
1936fn create_state ( module_name : & str , python_code : & str ) -> crate :: state:: state:: State {
2037 let mut test_env = TestEnv :: new ( ) ;
@@ -25,6 +42,17 @@ fn create_state(module_name: &str, python_code: &str) -> crate::state::state::St
2542 state
2643}
2744
45+ /// Create a type-checked state with the `__static__` stub and a test module.
46+ fn create_state_with_static ( module_name : & str , python_code : & str ) -> crate :: state:: state:: State {
47+ let mut test_env = TestEnv :: new ( ) ;
48+ test_env. add ( "__static__" , STATIC_MODULE_STUB ) ;
49+ test_env. add ( module_name, python_code) ;
50+ let ( state, _) = test_env
51+ . with_default_require_level ( Require :: Everything )
52+ . to_state ( ) ;
53+ state
54+ }
55+
2856/// Find the handle for a module by name in a transaction.
2957fn get_handle (
3058 module_name : & str ,
@@ -750,6 +778,149 @@ def f(t: tuple[Inner, str]) -> None:
750778 ) ;
751779}
752780
781+ /// BUG: When a literal int is assigned to a variable annotated with `__static__.int64`,
782+ /// the CinderX report should record the contextual type `__static__.int64` for the
783+ /// literal expression, but currently it records `Literal[42]` / `builtins.int` instead.
784+ #[ test]
785+ fn test_static_int64_literal_contextual_type ( ) {
786+ let state = create_state_with_static (
787+ "test" ,
788+ r#"
789+ from __static__ import int64
790+
791+ x: int64 = 42
792+ "# ,
793+ ) ;
794+ let transaction = state. transaction ( ) ;
795+ let handle = get_handle ( "test" , & transaction) ;
796+
797+ let data = collect_module_types ( & transaction, & handle) . expect ( "should collect types" ) ;
798+
799+ // The type table should contain `__static__.int64` as a class entry,
800+ // proving that the annotation resolved correctly.
801+ let has_int64_class = data. entries . iter ( ) . any ( |entry| {
802+ matches ! (
803+ & entry. ty,
804+ StructuredType :: Class { qname, .. } if qname == "__static__.int64"
805+ )
806+ } ) ;
807+ assert ! (
808+ has_int64_class,
809+ "expected `__static__.int64` in the type table, got: {:#?}" ,
810+ data. entries,
811+ ) ;
812+
813+ // BUG: The literal `42` should have contextual type `__static__.int64`, but
814+ // currently it gets `Literal[42]` with promoted type `builtins.int`.
815+ // Check that a Literal entry for "42" exists (the current, wrong behavior).
816+ let has_literal_42 = data. entries . iter ( ) . any ( |entry| {
817+ matches ! (
818+ & entry. ty,
819+ StructuredType :: Literal { value, .. } if value == "42"
820+ )
821+ } ) ;
822+ assert ! (
823+ has_literal_42,
824+ "expected Literal(42) in the type table (current buggy behavior), got: {:#?}" ,
825+ data. entries,
826+ ) ;
827+
828+ // Verify the literal's promoted type is builtins.int, NOT __static__.int64.
829+ // This confirms the bug: the contextual __static__ type is lost.
830+ let literal_entry = data
831+ . entries
832+ . iter ( )
833+ . find ( |entry| matches ! ( & entry. ty, StructuredType :: Literal { value, .. } if value == "42" ) )
834+ . expect ( "Literal(42) should exist" ) ;
835+ let promoted_idx = match & literal_entry. ty {
836+ StructuredType :: Literal { promoted_type, .. } => * promoted_type,
837+ _ => unreachable ! ( "already matched as Literal" ) ,
838+ } ;
839+ let promoted_entry = & data. entries [ promoted_idx] ;
840+ // BUG: This should be __static__.int64 but is currently builtins.int.
841+ assert ! (
842+ matches!(
843+ & promoted_entry. ty,
844+ StructuredType :: Class { qname, .. } if qname == "builtins.int"
845+ ) ,
846+ "expected promoted_type to be builtins.int (current buggy behavior), got: {:#?}" ,
847+ promoted_entry. ty,
848+ ) ;
849+ }
850+
851+ /// BUG: When a literal float is assigned to a variable annotated with `__static__.double`,
852+ /// the CinderX report should record the contextual type `__static__.double` for the
853+ /// literal expression, but currently it records `builtins.float` instead.
854+ #[ test]
855+ fn test_static_double_literal_contextual_type ( ) {
856+ let state = create_state_with_static (
857+ "test" ,
858+ r#"
859+ from __static__ import double
860+
861+ y: double = 3.14
862+ "# ,
863+ ) ;
864+ let transaction = state. transaction ( ) ;
865+ let handle = get_handle ( "test" , & transaction) ;
866+
867+ let data = collect_module_types ( & transaction, & handle) . expect ( "should collect types" ) ;
868+
869+ // The type table should contain `__static__.double` as a class entry,
870+ // proving that the annotation resolved correctly.
871+ let has_double_class = data. entries . iter ( ) . any ( |entry| {
872+ matches ! (
873+ & entry. ty,
874+ StructuredType :: Class { qname, .. } if qname == "__static__.double"
875+ )
876+ } ) ;
877+ assert ! (
878+ has_double_class,
879+ "expected `__static__.double` in the type table, got: {:#?}" ,
880+ data. entries,
881+ ) ;
882+
883+ // BUG: The literal `3.14` should have contextual type `__static__.double`, but
884+ // currently the located type for the literal expression resolves to `builtins.float`.
885+ // Check that a builtins.float class entry exists (used for the literal's type).
886+ let has_float_class = data. entries . iter ( ) . any ( |entry| {
887+ matches ! (
888+ & entry. ty,
889+ StructuredType :: Class { qname, args, .. } if qname == "builtins.float" && args. is_empty( )
890+ )
891+ } ) ;
892+ // BUG: This should be __static__.double but is currently builtins.float.
893+ assert ! (
894+ has_float_class,
895+ "expected `builtins.float` in the type table (current buggy behavior), got: {:#?}" ,
896+ data. entries,
897+ ) ;
898+
899+ // Find the located type entries whose type resolves to builtins.float.
900+ // At least one of these should correspond to the literal `3.14`, confirming
901+ // the bug that the contextual __static__.double type is lost.
902+ let float_class_idx = data
903+ . entries
904+ . iter ( )
905+ . position ( |entry| {
906+ matches ! (
907+ & entry. ty,
908+ StructuredType :: Class { qname, args, .. } if qname == "builtins.float" && args. is_empty( )
909+ )
910+ } )
911+ . expect ( "builtins.float should exist" ) ;
912+ let float_locations: Vec < _ > = data
913+ . locations
914+ . iter ( )
915+ . filter ( |loc| loc. type_index == float_class_idx)
916+ . collect ( ) ;
917+ assert ! (
918+ !float_locations. is_empty( ) ,
919+ "expected at least one location with type builtins.float (the literal 3.14), got locations: {:#?}" ,
920+ data. locations,
921+ ) ;
922+ }
923+
753924#[ test]
754925fn test_literal_promoted_type ( ) {
755926 let state = create_state ( "test" , "x = 42" ) ;
0 commit comments