From 9f748f3c80e692f76748c27322bf232ac07f8101 Mon Sep 17 00:00:00 2001 From: Arths17 Date: Mon, 6 Apr 2026 19:17:10 -0500 Subject: [PATCH 1/4] `no-matching-overload` false positive: expected type `_T | C[_T]` incorrectly constrains `__init__` overload resolution Fixes #3002 --- pyrefly/lib/alt/call.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index c89e8f0092..4531f48b8d 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -687,13 +687,32 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { ) -> Type { // Based on https://typing.readthedocs.io/en/latest/spec/constructors.html. let vs = if let Some(hint) = hint { - let vs = self - .solver() - .freshen_class_targs(cls.targs_mut(), self.uniques); - - self.is_subset_eq(&self.heap.mk_class_type(cls.clone()), hint.ty()); - self.solver().generalize_class_targs(cls.targs_mut()); - vs + // Constructor hints may be unions that contain non-instance branches + // (for example `T | Box[T]`). Constraining against the full union can + // bind unrelated type variables and over-specialize this constructor. + // Only pre-specialize from concrete instance branches of the same class. + let class_hint = hint.ty().clone().into_unions().into_iter().find_map(|ty| { + if let Type::ClassType(hint_cls) = ty { + let class_hint = Type::ClassType(hint_cls); + if class_hint.qname() == Some(cls.qname()) + && !class_hint.contains_type_variable() + && !class_hint.may_contain_quantified_var() + { + return Some(class_hint); + } + } + None + }); + if let Some(class_hint) = class_hint { + let vs = self + .solver() + .freshen_class_targs(cls.targs_mut(), self.uniques); + self.is_subset_eq(&self.heap.mk_class_type(cls.clone()), &class_hint); + self.solver().generalize_class_targs(cls.targs_mut()); + vs + } else { + QuantifiedHandle::empty() + } } else { QuantifiedHandle::empty() }; From c679958d94cc024ebf58f25fc6f83c44eb077878 Mon Sep 17 00:00:00 2001 From: Arths17 Date: Mon, 6 Apr 2026 19:21:58 -0500 Subject: [PATCH 2/4] modified: pyrefly/lib/alt/call.rs --- pyrefly/lib/alt/call.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index 4531f48b8d..4ef348b1fa 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -692,13 +692,12 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // bind unrelated type variables and over-specialize this constructor. // Only pre-specialize from concrete instance branches of the same class. let class_hint = hint.ty().clone().into_unions().into_iter().find_map(|ty| { - if let Type::ClassType(hint_cls) = ty { - let class_hint = Type::ClassType(hint_cls); - if class_hint.qname() == Some(cls.qname()) - && !class_hint.contains_type_variable() - && !class_hint.may_contain_quantified_var() + if matches!(ty, Type::ClassType(_)) { + if ty.qname() == Some(cls.qname()) + && !ty.contains_type_variable() + && !ty.may_contain_quantified_var() { - return Some(class_hint); + return Some(ty); } } None From 1309b17ecec1de7cada86cc4878c2bb725ba5a14 Mon Sep 17 00:00:00 2001 From: Arths17 Date: Mon, 6 Apr 2026 19:27:25 -0500 Subject: [PATCH 3/4] Avoid ambiguous constructor hint pre-specialization When a contextual hint contains multiple concrete branches of the same class (for example Box[int] | Box[str]), selecting the first branch makes constructor inference dependent on union member ordering and can reintroduce overload false positives. Only pre-specialize constructor class type arguments when there is exactly one concrete same-class hint branch, and add regression coverage for both the original inline union-hint case and the multi-branch ambiguity case. --- pyrefly/lib/alt/call.rs | 25 +++++++++++------- pyrefly/lib/test/constructors.rs | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index 4ef348b1fa..9537554a93 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -691,17 +691,24 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // (for example `T | Box[T]`). Constraining against the full union can // bind unrelated type variables and over-specialize this constructor. // Only pre-specialize from concrete instance branches of the same class. - let class_hint = hint.ty().clone().into_unions().into_iter().find_map(|ty| { - if matches!(ty, Type::ClassType(_)) { - if ty.qname() == Some(cls.qname()) + // If multiple concrete branches of the same class are present (for + // example `Box[int] | Box[str]`), skip pre-specialization entirely so + // constructor inference does not depend on union member ordering. + let mut matching_class_hints = hint + .ty() + .clone() + .into_unions() + .into_iter() + .filter(|ty| { + matches!(ty, Type::ClassType(_)) + && ty.qname() == Some(cls.qname()) && !ty.contains_type_variable() && !ty.may_contain_quantified_var() - { - return Some(ty); - } - } - None - }); + }); + let class_hint = match (matching_class_hints.next(), matching_class_hints.next()) { + (Some(class_hint), None) => Some(class_hint), + _ => None, + }; if let Some(class_hint) = class_hint { let vs = self .solver() diff --git a/pyrefly/lib/test/constructors.rs b/pyrefly/lib/test/constructors.rs index 1d3fcbaf92..eaa74a1559 100644 --- a/pyrefly/lib/test/constructors.rs +++ b/pyrefly/lib/test/constructors.rs @@ -793,6 +793,51 @@ B([A("oops")]) # E: `str` is not assignable to upper bound `A | int` of type va "#, ); +testcase!( + test_init_overload_inline_constructor_with_union_hint, + r#" +from collections.abc import Iterable, Mapping +from typing import Generic, Never, SupportsInt, TypeVar, overload + +TCo = TypeVar("TCo", covariant=True) +T = TypeVar("T") + +class Box(Generic[TCo]): + @overload + def __init__(self: "Box[Never]", val: Mapping[Never, SupportsInt], /) -> None: ... + @overload + def __init__(self: "Box[T]", val: Mapping[T, SupportsInt], /) -> None: ... + def __init__(self, val: object, /) -> None: + pass + +def process(items: Iterable[tuple[T | Box[T], int]]) -> Box[T]: ... + +process(((Box({1: 1}), 1),)) + "#, +); + +testcase!( + test_init_overload_inline_constructor_with_multiple_concrete_union_hints, + r#" +from collections.abc import Iterable +from typing import Generic, TypeVar, overload + +T = TypeVar("T") + +class Box(Generic[T]): + @overload + def __init__(self: "Box[int]", val: int, /) -> None: ... + @overload + def __init__(self: "Box[str]", val: str, /) -> None: ... + def __init__(self, val: int | str, /) -> None: + pass + +def process(items: Iterable[tuple[Box[int] | Box[str], int]]) -> None: ... + +process(((Box("x"), 1),)) + "#, +); + testcase!( test_init_overload_with_self, r#" From cf8a61bf24d256341e33301cf0accdf8b5a7b4f1 Mon Sep 17 00:00:00 2001 From: Arths17 Date: Mon, 6 Apr 2026 19:47:48 -0500 Subject: [PATCH 4/4] Format constructor hint filtering Apply rustfmt's line wrapping to the constructor hint filtering change so the branch passes formatting checks without altering behavior. --- pyrefly/lib/alt/call.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index 9537554a93..06fee8a548 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -694,12 +694,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // If multiple concrete branches of the same class are present (for // example `Box[int] | Box[str]`), skip pre-specialization entirely so // constructor inference does not depend on union member ordering. - let mut matching_class_hints = hint - .ty() - .clone() - .into_unions() - .into_iter() - .filter(|ty| { + let mut matching_class_hints = + hint.ty().clone().into_unions().into_iter().filter(|ty| { matches!(ty, Type::ClassType(_)) && ty.qname() == Some(cls.qname()) && !ty.contains_type_variable()