diff --git a/pyrefly/lib/alt/class/tuple.rs b/pyrefly/lib/alt/class/tuple.rs index 0b78ec2025..4adad43dc0 100644 --- a/pyrefly/lib/alt/class/tuple.rs +++ b/pyrefly/lib/alt/class/tuple.rs @@ -34,17 +34,13 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { } None } - - /// Check if a class that inherits from tuple defines its own `__getitem__`, - /// which should take priority over tuple's special subscript handling. - pub fn class_overrides_tuple_getitem(&self, cls: &ClassType) -> bool { - if cls.class_object().is_builtin("tuple") { - return false; - } + /// Check whether tuple indexing should use tuple's special subscript handling + /// rather than a user-defined `__getitem__` override. + pub fn tuple_uses_builtin_getitem(&self, cls: &ClassType) -> bool { self.get_non_synthesized_class_member_and_defining_class( cls.class_object(), &dunder::GETITEM, ) - .is_some_and(|member| !member.defining_class.is_builtin("tuple")) + .is_none_or(|member| member.defining_class.is_builtin("tuple")) } } diff --git a/pyrefly/lib/alt/expr.rs b/pyrefly/lib/alt/expr.rs index 48cb021c05..51adb6863f 100644 --- a/pyrefly/lib/alt/expr.rs +++ b/pyrefly/lib/alt/expr.rs @@ -2237,7 +2237,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { } Type::ClassType(ref cls) | Type::SelfType(ref cls) if let Some(tuple) = self.as_tuple(cls) - && !self.class_overrides_tuple_getitem(cls) => + && self.tuple_uses_builtin_getitem(cls) => { self.infer_tuple_subscript( tuple, diff --git a/pyrefly/lib/solver/subset.rs b/pyrefly/lib/solver/subset.rs index 2c8ab1eab7..33beebb3d6 100644 --- a/pyrefly/lib/solver/subset.rs +++ b/pyrefly/lib/solver/subset.rs @@ -1484,8 +1484,13 @@ impl<'a, Ans: LookupAnswer> Subset<'a, Ans> { metadata: _, }), ) => { - self.is_subset_params(&l.params, &u.params)?; - self.is_subset_eq(&l.ret, &u.ret) + // Check returns before parameters so quantified vars in the target callable + // pick up their covariant lower bounds before contravariant parameter checks. + // This keeps cases like `math.gcd <: Callable[[T, T], T]` precise: `int` + // from the return type should win over the looser `SupportsIndex` parameter + // bound. + self.is_subset_eq(&l.ret, &u.ret)?; + self.is_subset_params(&l.params, &u.params) } (Type::TypedDict(TypedDict::Anonymous(got)), Type::TypedDict(want)) => { self.is_subset_anonymous_typed_dict(got, want) diff --git a/pyrefly/lib/test/calls.rs b/pyrefly/lib/test/calls.rs index 9c5bee4b0e..5c932f633d 100644 --- a/pyrefly/lib/test/calls.rs +++ b/pyrefly/lib/test/calls.rs @@ -233,6 +233,21 @@ reduce(max, [1,2]) "#, ); +testcase!( + test_reduce_gcd_returns_int, + r#" +from functools import reduce +from math import gcd + +def detect_indentation(values: list[int]) -> int: + try: + indentation = reduce(gcd, [v for v in values if not v % 2]) or 1 + except TypeError: + indentation = 1 + return indentation + "#, +); + testcase!( test_union_with_type, r#" diff --git a/pyrefly/lib/test/lsp/lsp_interaction/pytorch_benchmark.rs b/pyrefly/lib/test/lsp/lsp_interaction/pytorch_benchmark.rs index cf56f84412..62429ffd53 100644 --- a/pyrefly/lib/test/lsp/lsp_interaction/pytorch_benchmark.rs +++ b/pyrefly/lib/test/lsp/lsp_interaction/pytorch_benchmark.rs @@ -48,7 +48,7 @@ fn test_pytorch_error_propagation_latency() { }; // Use all available cores for realistic benchmarking let mut interaction = - LspInteraction::new_with_args(args, NoTelemetry, Some(ThreadCount::AllThreads)); + LspInteraction::new_with_args(args, NoTelemetry, Some(ThreadCount::AllThreads), None); interaction.set_root(pytorch_root.clone()); interaction diff --git a/pyrefly/lib/test/tuple.rs b/pyrefly/lib/test/tuple.rs index 33aea38c58..888392ccef 100644 --- a/pyrefly/lib/test/tuple.rs +++ b/pyrefly/lib/test/tuple.rs @@ -561,7 +561,7 @@ testcase!( from typing import assert_type class Foo(tuple[int, ...]): - def __getitem__(self, name: str) -> int: # E: `Foo.__getitem__` has type + def __getitem__(self, name: str) -> int: # pyrefly: ignore [bad-override] ... def test(foo: Foo) -> None: @@ -575,7 +575,7 @@ testcase!( from typing import assert_type class Parent(tuple[int, ...]): - def __getitem__(self, name: str) -> int: # E: `Parent.__getitem__` has type + def __getitem__(self, name: str) -> int: # pyrefly: ignore [bad-override] ... class Child(Parent):