Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions pyrefly/lib/binding/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,7 @@ impl<'a> BindingsBuilder<'a> {
FlowStyle::MergeableImport(_)
| FlowStyle::Import(..)
| FlowStyle::ImportAs(_)
| FlowStyle::FunctionDef(..)
| FlowStyle::FunctionDef { .. }
| FlowStyle::ClassDef
| FlowStyle::LoopRecursion => None,
}
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion pyrefly/lib/binding/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
);
}
}
Expand Down
52 changes: 48 additions & 4 deletions pyrefly/lib/binding/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<KeyDecoratedFunction>, bool),
/// Track whether the return type has an explicit annotation and whether this
/// definition is marked as an overload.
FunctionDef {
function_idx: Idx<KeyDecoratedFunction>,
has_return_annotation: bool,
is_overload: bool,
},
/// Am I a class definition?
ClassDef,
/// The name is possibly uninitialized (perhaps due to merging branches)
Expand Down Expand Up @@ -1263,7 +1268,10 @@ impl Scopes {
name: &Name,
) -> Option<(Idx<Key>, Idx<KeyDecoratedFunction>)> {
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));
}
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions pyrefly/lib/test/overload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down
10 changes: 10 additions & 0 deletions pyrefly/lib/test/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading