diff --git a/pyrefly/lib/binding/bindings.rs b/pyrefly/lib/binding/bindings.rs index 71c30efb93..a0cdb1341c 100644 --- a/pyrefly/lib/binding/bindings.rs +++ b/pyrefly/lib/binding/bindings.rs @@ -857,7 +857,7 @@ impl<'a> BindingsBuilder<'a> { FlowStyle::MergeableImport(_) | FlowStyle::Import(..) | FlowStyle::ImportAs(_) - | FlowStyle::FunctionDef(..) + | FlowStyle::FunctionDef { .. } | FlowStyle::ClassDef | FlowStyle::LoopRecursion => None, } @@ -929,7 +929,13 @@ impl<'a> BindingsBuilder<'a> { } pub fn lookup_name(&mut self, name: Hashed<&Name>, usage: &mut Usage) -> NameLookupResult { - match self.scopes.look_up_name_for_read(name) { + let name_read_info = if matches!(usage, Usage::StaticTypeInformation) { + self.scopes + .look_up_name_for_read_in_static_type_context(name) + } else { + self.scopes.look_up_name_for_read(name) + }; + match name_read_info { NameReadInfo::Flow { idx, initialized: is_initialized, diff --git a/pyrefly/lib/binding/function.rs b/pyrefly/lib/binding/function.rs index 4f7281e15e..1594441713 100644 --- a/pyrefly/lib/binding/function.rs +++ b/pyrefly/lib/binding/function.rs @@ -748,7 +748,11 @@ impl<'a> BindingsBuilder<'a> { &func_name, def_idx, Binding::Function(function_idx, pred_idx, metadata_key), - FlowStyle::FunctionDef(function_idx, return_ann_with_range.is_some()), + FlowStyle::FunctionDef { + function_idx, + has_return_annotation: return_ann_with_range.is_some(), + is_overload: decorators.is_overload, + }, ); } } diff --git a/pyrefly/lib/binding/scope.rs b/pyrefly/lib/binding/scope.rs index e60cbe4ede..4559bb2606 100644 --- a/pyrefly/lib/binding/scope.rs +++ b/pyrefly/lib/binding/scope.rs @@ -603,8 +603,13 @@ pub enum FlowStyle { /// would get `foo.bar` here. ImportAs(ModuleName), /// Am I a function definition? Used to chain overload definitions. - /// If so, does my return type have an explicit annotation? - FunctionDef(Idx, bool), + /// Track whether the return type has an explicit annotation and whether this + /// definition is marked as an overload. + FunctionDef { + function_idx: Idx, + has_return_annotation: bool, + is_overload: bool, + }, /// Am I a class definition? ClassDef, /// The name is possibly uninitialized (perhaps due to merging branches) @@ -1263,7 +1268,10 @@ impl Scopes { name: &Name, ) -> Option<(Idx, Idx)> { if let Some(value) = self.current().flow.get_value(name) - && let FlowStyle::FunctionDef(fidx, _) = value.style + && let FlowStyle::FunctionDef { + function_idx: fidx, + .. + } = value.style { return Some((value.idx, fidx)); } @@ -2089,7 +2097,10 @@ impl Scopes { // Mutable captures are not actually owned by the class scope, and do not become attributes. } else if let Some(value) = class_body.flow.get_info_hashed(name).and_then(|flow| flow.value()) { let definition = match &value.style { - FlowStyle::FunctionDef(_, has_return_annotation) => ClassFieldDefinition::MethodLike { + FlowStyle::FunctionDef { + has_return_annotation, + .. + } => ClassFieldDefinition::MethodLike { definition: value.idx, has_return_annotation: *has_return_annotation, }, @@ -2251,12 +2262,45 @@ impl Scopes { /// Look up the information needed to create a `Usage` binding for a read of a name /// in the current scope stack. pub fn look_up_name_for_read(&self, name: Hashed<&Name>) -> NameReadInfo { + self.look_up_name_for_read_impl(name, false) + } + + /// Look up a name for a static type context (annotations, type aliases, etc). + /// Skips class-scope overload definitions so annotations resolve to types. + pub fn look_up_name_for_read_in_static_type_context( + &self, + name: Hashed<&Name>, + ) -> NameReadInfo { + self.look_up_name_for_read_impl(name, true) + } + + fn look_up_name_for_read_impl( + &self, + name: Hashed<&Name>, + skip_class_overload_function_definitions: bool, + ) -> NameReadInfo { self.visit_scopes(|_, scope, flow_barrier| { let is_class = matches!(scope.kind, ScopeKind::Class(_)); if let Some(flow_info) = scope.flow.get_info_hashed(name) && flow_barrier < FlowBarrier::BlockFlow { + if skip_class_overload_function_definitions + && is_class + && flow_info + .value() + .is_some_and(|value| { + matches!( + value.style, + FlowStyle::FunctionDef { + is_overload: true, + .. + } + ) + }) + { + return None; + } let initialized = if flow_barrier == FlowBarrier::AllowFlowUnchecked { // Just assume the name is initialized without checking. InitializedInFlow::Yes diff --git a/pyrefly/lib/test/overload.rs b/pyrefly/lib/test/overload.rs index 8c5a4cc6eb..7795bdc7fc 100644 --- a/pyrefly/lib/test/overload.rs +++ b/pyrefly/lib/test/overload.rs @@ -110,6 +110,27 @@ def test(o: C): "#, ); +testcase!( + test_overload_method_name_matches_class, + r#" +from typing import assert_type, overload + +class A: + @overload + def B(self, x: int) -> B: ... + @overload + def B(self, x: str) -> B: ... + def B(self, x): + return B() + +class B: + x: int + +assert_type(A().B(0).x, int) +assert_type(A().B("1").x, int) + "#, +); + testcase!( test_overload_arg_errors, r#" diff --git a/pyrefly/lib/test/scope.rs b/pyrefly/lib/test/scope.rs index 8f853ea495..de94267b86 100644 --- a/pyrefly/lib/test/scope.rs +++ b/pyrefly/lib/test/scope.rs @@ -786,6 +786,16 @@ class C: "#, ); +testcase!( + test_class_scope_annotation_shadows_function, + r#" +class D: + def int(self) -> None: + ... + y: int = 0 # E: Expected a type form +"#, +); + // Nested scopes - except for parameter scopes - cannot see a containing class // body. This applies not only to methods but also other scopes like lambda, inner // class bodies, and comprehensions. See https://github.com/facebook/pyrefly/issues/264