File tree Expand file tree Collapse file tree 2 files changed +36
-2
lines changed
crates/ty_python_semantic Expand file tree Collapse file tree 2 files changed +36
-2
lines changed Original file line number Diff line number Diff line change 1+ # Narrowing using ` hasattr() `
2+
3+ The builtin function ` hasattr() ` can be used to narrow nominal and structural types. This is
4+ accomplished using an intersection with a synthesized protocol:
5+
6+ ``` py
7+ from typing import final
8+
9+ class Foo : ...
10+
11+ @final
12+ class Bar : ...
13+
14+ def f (x : Foo):
15+ if hasattr (x, " spam" ):
16+ reveal_type(x) # revealed: Foo & <Protocol with members 'spam'>
17+ reveal_type(x.spam) # revealed: object
18+
19+ if hasattr (x, " not-an-identifier" ):
20+ reveal_type(x) # revealed: Foo
21+
22+ def y (x : Bar):
23+ if hasattr (x, " spam" ):
24+ reveal_type(x) # revealed: Never
25+ reveal_type(x.spam) # revealed: Never
26+ ```
Original file line number Diff line number Diff line change @@ -11,6 +11,9 @@ use crate::types::{
1111 UnionBuilder ,
1212} ;
1313use crate :: Db ;
14+
15+ use ruff_python_stdlib:: identifiers:: is_identifier;
16+
1417use itertools:: Itertools ;
1518use ruff_python_ast as ast;
1619use ruff_python_ast:: { BoolOp , ExprBoolOp } ;
@@ -718,11 +721,16 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
718721 if function == KnownFunction :: HasAttr {
719722 let attr = inference
720723 . expression_type ( second_arg. scoped_expression_id ( self . db , scope) )
721- . into_string_literal ( ) ?;
724+ . into_string_literal ( ) ?
725+ . value ( self . db ) ;
726+
727+ if !is_identifier ( attr) {
728+ return None ;
729+ }
722730
723731 let constraint = Type :: synthesized_protocol (
724732 self . db ,
725- [ ( attr. value ( self . db ) , KnownClass :: Object . to_instance ( self . db ) ) ] ,
733+ [ ( attr, KnownClass :: Object . to_instance ( self . db ) ) ] ,
726734 ) ;
727735
728736 return Some ( NarrowingConstraints :: from_iter ( [ (
You can’t perform that action at this time.
0 commit comments