1212 */
1313
1414use pyrefly_types:: callable:: FuncMetadata ;
15+ use pyrefly_types:: type_alias:: TypeAliasData ;
1516use pyrefly_types:: types:: Union ;
1617use pyrefly_util:: visit:: Visit ;
1718use pyrefly_util:: visit:: VisitMut ;
@@ -39,8 +40,6 @@ use crate::types::callable::FunctionKind;
3940use crate :: types:: callable:: unexpected_keyword;
4041use crate :: types:: class:: Class ;
4142use crate :: types:: class:: ClassType ;
42- use crate :: types:: special_form:: SpecialForm ;
43- use crate :: types:: special_form:: SpecialForm ;
4443use crate :: types:: tuple:: Tuple ;
4544use crate :: types:: types:: Type ;
4645
@@ -338,7 +337,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
338337 }
339338 for keyword in & call. arguments . keywords {
340339 if let Some ( arg) = & keyword. arg
341- && matches ! ( arg. as_str( ) , "__type_pos" | " type_")
340+ && matches ! ( arg. as_str( ) , "type_" )
342341 && let Some ( ty) = self . python_type_from_type_engine_expr ( & keyword. value )
343342 {
344343 return Some ( ty) ;
@@ -368,7 +367,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
368367 _ => { }
369368 }
370369 }
371- if primary_key { false } else { nullable }
370+ ! primary_key && nullable
372371 }
373372
374373 fn expr_bool_literal ( expr : & Expr ) -> Option < bool > {
@@ -391,61 +390,70 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
391390 self . python_type_from_type_engine_type ( & inst)
392391 }
393392 Type :: Type ( inner) => self . python_type_from_type_engine_type ( inner) ,
394- Type :: TypeAlias ( alias) => self . python_type_from_type_engine_type ( & alias. as_type ( ) ) ,
393+ Type :: TypeAlias ( alias) => match alias. as_ref ( ) {
394+ TypeAliasData :: Value ( alias) => {
395+ self . python_type_from_type_engine_type ( & alias. as_type ( ) )
396+ }
397+ TypeAliasData :: Ref ( _) => {
398+ self . python_type_from_type_engine_type ( & self . untype_alias ( alias) )
399+ }
400+ } ,
395401 Type :: Union ( u) => {
396- let mut inferred = None ;
402+ let mut inferred = Vec :: new ( ) ;
397403 for member in & u. members {
398404 if let Some ( member_ty) = self . python_type_from_type_engine_type ( member) {
399- match & inferred {
400- Some ( existing) if existing != & member_ty => return None ,
401- None => inferred = Some ( member_ty) ,
402- _ => { }
403- }
405+ inferred. push ( member_ty) ;
406+ }
407+ }
408+ if inferred. is_empty ( ) {
409+ None
410+ } else {
411+ inferred. sort ( ) ;
412+ inferred. dedup ( ) ;
413+ if inferred. len ( ) == 1 {
414+ inferred. pop ( )
415+ } else {
416+ Some ( Type :: union ( inferred) )
404417 }
405418 }
406- inferred
407419 }
408420 _ => None ,
409421 }
410422 }
411423
412424 fn python_type_from_type_engine_class ( & self , cls : & ClassType ) -> Option < Type > {
413- if Self :: is_sqlalchemy_type_engine_class ( cls. class_object ( ) ) {
425+ if cls
426+ . class_object ( )
427+ . has_toplevel_qname ( "sqlalchemy.sql.type_api" , "TypeEngine" )
428+ {
414429 return cls. targs ( ) . as_slice ( ) . first ( ) . cloned ( ) ;
415430 }
416- let bases = self . get_base_types_for_class ( cls. class_object ( ) ) ;
417- for base in bases. iter ( ) {
418- if let Some ( ty) = self . python_type_from_type_engine_class ( base) {
419- return Some ( ty) ;
431+ let mro = self . get_mro_for_class ( cls. class_object ( ) ) ;
432+ for ancestor in mro. ancestors_no_object ( ) {
433+ if ancestor
434+ . class_object ( )
435+ . has_toplevel_qname ( "sqlalchemy.sql.type_api" , "TypeEngine" )
436+ {
437+ return ancestor. targs ( ) . as_slice ( ) . first ( ) . cloned ( ) ;
420438 }
421439 }
422440 None
423441 }
424442
425- fn is_sqlalchemy_type_engine_class ( class : & Class ) -> bool {
426- class. has_toplevel_qname ( "sqlalchemy.sql.type_api" , "TypeEngine" )
427- }
428-
429443 fn apply_sqlalchemy_mapped_python_type ( & self , mut ty : Type , python_type : Type ) -> Type {
430444 ty. visit_mut ( & mut |inner| {
431445 if let Type :: ClassType ( class_type) = inner
432- && Self :: is_sqlalchemy_mapped_class ( class_type. class_object ( ) )
446+ && class_type
447+ . class_object ( )
448+ . has_toplevel_qname ( "sqlalchemy.orm.properties" , "MappedColumn" )
449+ && let Some ( slot) = class_type. targs_mut ( ) . as_mut ( ) . get_mut ( 0 )
433450 {
434- if let Some ( slot) = class_type. targs_mut ( ) . as_mut ( ) . get_mut ( 0 ) {
435- * slot = python_type. clone ( ) ;
436- }
451+ * slot = python_type. clone ( ) ;
437452 }
438453 } ) ;
439454 ty
440455 }
441456
442- fn is_sqlalchemy_mapped_class ( class : & Class ) -> bool {
443- class. has_toplevel_qname ( "sqlalchemy.orm.base" , "Mapped" )
444- || class. has_toplevel_qname ( "sqlalchemy.orm.base" , "_MappedAnnotationBase" )
445- || class. has_toplevel_qname ( "sqlalchemy.orm.base" , "_DeclarativeMapped" )
446- || class. has_toplevel_qname ( "sqlalchemy.orm.properties" , "MappedColumn" )
447- }
448-
449457 pub fn call_isinstance (
450458 & self ,
451459 obj : & Expr ,
0 commit comments