Skip to content

fix Preliminary support for class decorators #2752#2773

Open
asukaminato0721 wants to merge 1 commit intofacebook:mainfrom
asukaminato0721:2752
Open

fix Preliminary support for class decorators #2752#2773
asukaminato0721 wants to merge 1 commit intofacebook:mainfrom
asukaminato0721:2752

Conversation

@asukaminato0721
Copy link
Contributor

Summary

Fixes #2752

applying class decorators to the runtime class-value bindingm while preserving the original class object when a decorator still returns a usable type form.

Test Plan

add test

@meta-cla meta-cla bot added the cla signed label Mar 11, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review March 11, 2026 01:45
Copilot AI review requested due to automatic review settings March 11, 2026 01:45
@github-actions

This comment has been minimized.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances Pyrefly’s solver to type-check class decorator application so the decorated class name can be rebound to the decorator’s return type (e.g., @remote turning a class name into an ActorHandle[...]), and adds a regression test covering this behavior.

Changes:

  • Apply non-dataclass-transform class decorators during Binding::ClassDef type solving to infer the post-decoration value type.
  • Extend untype_opt/expr_untype handling for Type::KwCall and name lookups to better support the new decorator behavior.
  • Add a new decorator test case ensuring a class decorator can rebind the class value.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pyrefly/lib/test/decorators.rs Adds a regression testcase for a class decorator that rebinds the class symbol to an ActorHandle[T].
pyrefly/lib/alt/solve.rs Implements class-decorator application during solving and adjusts untyping/name handling to support the new behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

None,
None,
);
if self.untype_opt(ty.clone(), range, errors).is_some() {
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untype_opt is being used here purely as a predicate to detect whether the decorator result is a type form, but untype_opt calls canonicalize_all_class_types, which can emit user-facing errors (e.g., implicit-Any errors for tuple/Callable). That can lead to spurious errors at class decoration sites when a decorator returns something that happens to be untypeable, even though we’re not actually interpreting the result as an annotation. Consider running this check with an ErrorStyle::Never collector (via error_swallower()), or adding a side-effect-free helper for “is this a type form?” so the predicate doesn’t report errors.

Suggested change
if self.untype_opt(ty.clone(), range, errors).is_some() {
let mut swallower = error_swallower();
if self
.untype_opt(ty.clone(), range, &mut swallower)
.is_some()
{

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

pandera (https://github.com/pandera-dev/pandera)
- ERROR tests/pyspark/test_schemas_on_pyspark_pandas.py:69:36-60: Argument `type[Timedelta64]` is not assignable to parameter `element` with type `type[Category] | type[pandera.engines.numpy_engine.Complex128@310:11-21] | type[pandera.engines.numpy_engine.Complex128@318:11-21] | type[Complex64] | type[Date] | type[Float16] | type[Interval] | type[Object] | type[Period] | type[Sparse] | type[UINT16] | type[UINT32] | type[UINT64] | type[UINT8] | type[UInt16] | type[UInt32] | type[UInt64] | type[UInt8]` in function `set.add` [bad-argument-type]
- ERROR tests/pyspark/test_schemas_on_pyspark_pandas.py:73:9-57: Argument `set[type[Complex256] | type[Float128]]` is not assignable to parameter `*s` with type `Iterable[type[Category] | type[pandera.engines.numpy_engine.Complex128@310:11-21] | type[pandera.engines.numpy_engine.Complex128@318:11-21] | type[Complex64] | type[Date] | type[Float16] | type[Interval] | type[Object] | type[Period] | type[Sparse] | type[UINT16] | type[UINT32] | type[UINT64] | type[UINT8] | type[UInt16] | type[UInt32] | type[UInt64] | type[UInt8]]` in function `set.update` [bad-argument-type]

scikit-learn (https://github.com/scikit-learn/scikit-learn)
- ERROR sklearn/linear_model/tests/test_passive_aggressive.py:298:9-22: Object of class `PassiveAggressiveRegressor` has no attribute `transform` [missing-attribute]
+ ERROR sklearn/utils/tests/test_deprecation.py:40:18-28: Invalid base class: `((*args: Unknown, **kwargs: Unknown) -> Unknown) | Unknown` [invalid-inheritance]

steam.py (https://github.com/Gobot1234/steam.py)
+  WARN steam/abc.py:1013:7-14: Fixpoint iteration did not converge. Inferred result `[MessageT: Message = Message, ClanT: Clan | None = Clan | None, GroupT: Group | None = Group | None]`. Adding annotations may help. [non-convergent-recursion]
+  WARN steam/abc.py:1028:7-14: Fixpoint iteration did not converge. Inferred result `[UserT: PartialUser = PartialUser, ChannelT: Channel = Channel]`. Adding annotations may help. [non-convergent-recursion]

pandas (https://github.com/pandas-dev/pandas)
- ERROR pandas/core/frame.py:636:56-60: Argument `list[Unknown] | Unknown` is not assignable to parameter `value` with type `Interval[Unknown] | Timedelta | Timestamp | bool | bytes | complex | complexfloating | date | datetime | datetime64 | float | floating | int | integer | str | timedelta | timedelta64` in function `pandas.core.dtypes.cast.construct_1d_arraylike_from_scalar` [bad-argument-type]
- ERROR pandas/core/frame.py:642:21-25: Argument `list[Unknown] | Unknown` is not assignable to parameter `value` with type `Interval[Unknown] | Timedelta | Timestamp | bool | bytes | complex | complexfloating | date | datetime | datetime64 | float | floating | int | integer | str | timedelta | timedelta64` in function `pandas.core.dtypes.cast.construct_2d_arraylike_from_scalar` [bad-argument-type]
- ERROR pandas/tests/frame/test_constructors.py:3165:35-45: Argument `set[str]` is not assignable to parameter `index` with type `ExtensionArray | Index | SequenceNotStr[Unknown] | Series | ndarray | range | None` in function `pandas.core.frame.DataFrame.__init__` [bad-argument-type]
- ERROR pandas/tests/frame/test_constructors.py:3167:37-52: Argument `set[str]` is not assignable to parameter `columns` with type `ExtensionArray | Index | SequenceNotStr[Unknown] | Series | ndarray | range | None` in function `pandas.core.frame.DataFrame.__init__` [bad-argument-type]

core (https://github.com/home-assistant/core)
+ ERROR homeassistant/helpers/service.py:464:12-60: Returned type `homeassistant.helpers.target.SelectedEntities` is not assignable to declared return type `homeassistant.helpers.service.SelectedEntities` [bad-return]

sphinx (https://github.com/sphinx-doc/sphinx)
-  WARN sphinx/builders/linkcheck.py:756:29-42: Identity comparison between an instance of `dict[Pattern[str], Pattern[str]]` and class `_SENTINEL_LAR` is always False [unnecessary-comparison]

spack (https://github.com/spack/spack)
- ERROR lib/spack/spack/audit.py:1137:52-91: Argument `Unknown | None` is not assignable to parameter `other` with type `ArchSpec` in function `spack.spec.ArchSpec.satisfies` [bad-argument-type]
+ ERROR lib/spack/spack/audit.py:1127:29-61: Object of class `FunctionType` has no attribute `default_arch` [missing-attribute]
+ ERROR lib/spack/spack/bootstrap/clingo.py:45:12-44: Object of class `FunctionType` has no attribute `default_arch` [missing-attribute]
+ ERROR lib/spack/spack/bootstrap/clingo.py:56:34-66: Object of class `FunctionType` has no attribute `default_arch` [missing-attribute]
+ ERROR lib/spack/spack/bootstrap/core.py:153:33-57: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/bootstrap/core.py:176:66-90: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/bootstrap/core.py:196:28-52: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
- ERROR lib/spack/spack/builder.py:222:16-55: Returned type `bool | str | tuple[bool | str, ...]` is not assignable to declared return type `str` [bad-return]
- ERROR lib/spack/spack/cmd/__init__.py:518:34-44: Argument `tuple[Literal[''], None]` is not assignable to parameter `object` with type `tuple[str, Spec]` in function `list.append` [bad-argument-type]
- ERROR lib/spack/spack/cmd/arch.py:111:23-41: Object of class `NoneType` has no attribute `family` [missing-attribute]
- ERROR lib/spack/spack/cmd/arch.py:113:23-42: Object of class `NoneType` has no attribute `generic` [missing-attribute]
+ ERROR lib/spack/spack/compilers/config.py:224:16-35: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/concretize.py:275:68-76: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/database.py:1002:31-56: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/database.py:1047:31-56: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/database.py:1860:30-55: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/graph.py:448:27-52: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/graph.py:462:32-57: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/graph.py:545:21-53: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/graph.py:546:21-28: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/graph.py:547:21-29: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/main.py:827:35-67: Object of class `FunctionType` has no attribute `default_arch` [missing-attribute]
+ ERROR lib/spack/spack/operating_systems/freebsd.py:11:17-32: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/operating_systems/freebsd.py:11:17-32: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/operating_systems/linux_distro.py:20:19-34: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/operating_systems/linux_distro.py:20:19-34: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/operating_systems/mac_os.py:109:13-28: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/operating_systems/mac_os.py:109:13-28: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/operating_systems/windows_os.py:28:17-32: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/operating_systems/windows_os.py:28:17-32: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/platforms/__init__.py:34:20-28: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/platforms/__init__.py:56:37-45: Expected class object, got `(realf: Unknown) -> Unknown` [invalid-argument]
+ ERROR lib/spack/spack/platforms/_platform.py:42:20-45: Object of class `FunctionType` has no attribute `reserved_targets` [missing-attribute]
+ ERROR lib/spack/spack/platforms/_platform.py:54:20-45: Object of class `FunctionType` has no attribute `deprecated_names` [missing-attribute]
+ ERROR lib/spack/spack/platforms/_platform.py:57:20-45: Object of class `FunctionType` has no attribute `reserved_targets` [missing-attribute]
+ ERROR lib/spack/spack/platforms/_platform.py:63:20-41: Object of class `FunctionType` has no attribute `reserved_oss` [missing-attribute]
+ ERROR lib/spack/spack/platforms/_platform.py:63:44-69: Object of class `FunctionType` has no attribute `deprecated_names` [missing-attribute]
+ ERROR lib/spack/spack/platforms/_platform.py:75:20-45: Object of class `FunctionType` has no attribute `deprecated_names` [missing-attribute]
+ ERROR lib/spack/spack/platforms/_platform.py:78:20-41: Object of class `FunctionType` has no attribute `reserved_oss` [missing-attribute]
+ ERROR lib/spack/spack/platforms/darwin.py:13:14-22: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/platforms/darwin.py:13:14-22: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/platforms/freebsd.py:11:15-23: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/platforms/freebsd.py:11:15-23: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/platforms/linux.py:11:13-21: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/platforms/linux.py:11:13-21: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/platforms/test.py:13:12-20: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/platforms/test.py:13:12-20: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/platforms/test.py:25:96-97: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/platforms/test.py:26:96-97: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/platforms/windows.py:12:15-23: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/platforms/windows.py:12:15-23: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/solver/asp.py:1641:39-43: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/solver/asp.py:3501:39-41: Missing argument `realf` [missing-argument]
+ ERROR lib/spack/spack/solver/asp.py:3521:35-67: Object of class `FunctionType` has no attribute `from_concretizer` [missing-attribute]
+ ERROR lib/spack/spack/solver/requirements.py:318:35-60: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:269:47-55: Expected class object, got `(realf: Unknown) -> Unknown` [invalid-argument]
+ ERROR lib/spack/spack/spec.py:271:30-44: Object of class `tuple` has no attribute `platform` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:271:46-54: Object of class `tuple` has no attribute `os` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:271:56-68: Object of class `tuple` has no attribute `target` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:293:32-34: Missing argument `realf` [missing-argument]
+ ERROR lib/spack/spack/spec.py:305:34-42: Expected class object, got `(realf: Unknown) -> Unknown` [invalid-argument]
+ ERROR lib/spack/spack/spec.py:344:21-58: Object of class `FunctionType` has no attribute `reserved_oss` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:382:26-67: Object of class `FunctionType` has no attribute `reserved_targets` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:398:33-41: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:420:34-42: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:441:41-49: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:455:28-47: Object of class `FunctionType` has no attribute `ANY_TARGET` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:460:41-49: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:564:33-41: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
- ERROR lib/spack/spack/spec.py:1009:9-18: Class member `FlagMap._cmp_iter` overrides parent class `HashableMap` in an inconsistent manner [bad-override]
+ ERROR lib/spack/spack/spec.py:641:32-53: Object of class `FunctionType` has no attribute `default_arch` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:784:81-95: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:790:13-22: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/spec.py:791:13-20: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:792:13-21: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:793:13-24: Unexpected keyword argument `propagation` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:794:13-19: Unexpected keyword argument `direct` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:795:13-17: Unexpected keyword argument `when` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:798:34-48: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:868:24-38: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/spec.py:870:30-872:10: Missing argument `realf` [missing-argument]
+ ERROR lib/spack/spack/spec.py:871:13-19: Unexpected keyword argument `parent` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:871:31-35: Unexpected keyword argument `spec` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:871:49-56: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:871:71-79: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:930:15-56: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/spec.py:930:15-56: `(realf: Unknown) -> Unknown` is not subscriptable [unsupported-operation]
+ ERROR lib/spack/spack/spec.py:1488:26-47: Object of class `FunctionType` has no attribute `default_arch` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:1673:31-81: Missing argument `realf` [missing-argument]
+ ERROR lib/spack/spack/spec.py:1673:32-38: Unexpected keyword argument `parent` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1673:47-51: Unexpected keyword argument `spec` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1673:58-65: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1673:69-77: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1778:35-70: Object of class `FunctionType` has no attribute `from_string_or_bool` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:1934:13-28: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/spec.py:1935:13-20: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1936:13-21: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1937:13-19: Unexpected keyword argument `direct` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1938:13-24: Unexpected keyword argument `propagation` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:1939:13-17: Unexpected keyword argument `when` [unexpected-keyword]
+ ERROR lib/spack/spack/spec.py:2587:37-54: Object of class `FunctionType` has no attribute `override` [missing-attribute]
- ERROR lib/spack/spack/spec.py:3662:31-87: Object of class `VariantValue` has no attribute `_patches_in_order_of_appearance` [missing-attribute]
- ERROR lib/spack/spack/spec.py:3717:9-33: Object of class `FlagMap` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/spec.py:3731:17-65: Object of class `VariantValue` has no attribute `_patches_in_order_of_appearance` [missing-attribute]
- ERROR lib/spack/spack/spec.py:3733:9-27: Object of class `VariantMap` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/spec.py:5180:9-20: Class member `VariantMap.__setitem__` overrides parent class `HashableMap` in an inconsistent manner [bad-param-name-override]
+ ERROR lib/spack/spack/spec.py:5176:18-56: Invalid base class: `((realf: Unknown) -> Unknown) | Unknown` [invalid-inheritance]
+ ERROR lib/spack/spack/spec.py:5176:18-56: `(realf: Unknown) -> Unknown` is not subscriptable [unsupported-operation]
+ ERROR lib/spack/spack/spec.py:5182:34-49: Expected class object, got `(realf: Unknown) -> Unknown` [invalid-argument]
+ ERROR lib/spack/spack/spec.py:5428:33-51: Object of class `FunctionType` has no attribute `from_dict` [missing-attribute]
- ERROR lib/spack/spack/spec.py:5467:17-53: Object of class `VariantValue` has no attribute `_patches_in_order_of_appearance` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:5439:39-69: Object of class `FunctionType` has no attribute `from_node_dict` [missing-attribute]
+ ERROR lib/spack/spack/spec.py:5859:18-33: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/test/architecture.py:78:70-75: Expected 1 positional argument, got 2 [bad-argument-count]
- ERROR lib/spack/spack/test/concretization/core.py:1287:24-56: Argument `bool | str | tuple[bool | str, ...]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
- ERROR lib/spack/spack/test/concretization/core.py:1605:43-75: No matching overload found for function `set.__init__` called with arguments: (bool | str | tuple[bool | str, ...]) [no-matching-overload]
- ERROR lib/spack/spack/test/concretization/core.py:1618:19-53: No matching overload found for function `set.__init__` called with arguments: (bool | str | tuple[bool | str, ...]) [no-matching-overload]
- ERROR lib/spack/spack/test/config.py:409:30-48: Object of class `NoneType` has no attribute `family` [missing-attribute]
- ERROR lib/spack/spack/test/conftest.py:842:25-40: Object of class `NoneType` has no attribute `name` [missing-attribute]
- ERROR lib/spack/spack/test/conftest.py:842:42-60: Object of class `NoneType` has no attribute `version` [missing-attribute]
- ERROR lib/spack/spack/test/patch.py:173:9-65: Object of class `VariantValue` has no attribute `_patches_in_order_of_appearance` [missing-attribute]
- ERROR lib/spack/spack/test/patch.py:183:12-57: `in` is not supported between `Literal['2f2b087a8f84834fd03d4d1d5b43584011e869e4657504ef3f8b0a672a5c222e']` and `bool` [not-iterable]
- ERROR lib/spack/spack/test/patch.py:183:12-57: `in` is not supported between `Literal['a69b288d7393261e613c276c6d38a01461028291f6e381623acc58139d01f54d']` and `bool` [not-iterable]
- ERROR lib/spack/spack/test/patch.py:186:12-61: `not in` is not supported between `Literal['2f2b087a8f84834fd03d4d1d5b43584011e869e4657504ef3f8b0a672a5c222e']` and `bool` [not-iterable]
- ERROR lib/spack/spack/test/patch.py:186:12-61: `not in` is not supported between `Literal['a69b288d7393261e613c276c6d38a01461028291f6e381623acc58139d01f54d']` and `bool` [not-iterable]
- ERROR lib/spack/spack/test/patch.py:209:19-74: Object of class `VariantValue` has no attribute `_patches_in_order_of_appearance` [missing-attribute]
- ERROR lib/spack/spack/test/patch.py:359:12-72: `not in` is not supported between `Literal['bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c']` and `bool` [not-iterable]
- ERROR lib/spack/spack/test/patch.py:359:12-72: `not in` is not supported between `Literal['d30392e66c636a063769cbb1db08cd3455a424650d4494db6379d73ea799582b']` and `bool` [not-iterable]
+ ERROR lib/spack/spack/test/spec_semantics.py:2036:36-41: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/test/spec_semantics.py:2036:43-50: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2036:54-62: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2037:36-41: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/test/spec_semantics.py:2037:43-50: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2037:54-62: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2045:35-40: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/test/spec_semantics.py:2045:42-49: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2045:53-61: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2337:36-41: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/test/spec_semantics.py:2337:43-50: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2337:54-62: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2337:67-71: Unexpected keyword argument `when` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2338:36-41: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/test/spec_semantics.py:2338:43-50: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2338:54-62: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/test/spec_semantics.py:2435:35-40: Expected 1 positional argument, got 2 [bad-argument-count]
+ ERROR lib/spack/spack/test/spec_semantics.py:2435:42-49: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/test/variant.py:345:9-36: Object of class `FunctionType` has no attribute `from_node_dict` [missing-attribute]
+ ERROR lib/spack/spack/test/variant.py:348:9-36: Object of class `FunctionType` has no attribute `from_node_dict` [missing-attribute]
+ ERROR lib/spack/spack/test/variant.py:351:9-36: Object of class `FunctionType` has no attribute `from_node_dict` [missing-attribute]
- ERROR lib/spack/spack/traverse.py:236:49-53: Argument `None` is not assignable to parameter `parent` with type `Spec` in function `spack.spec.DependencySpec.__init__` [bad-argument-type]
+ ERROR lib/spack/spack/traverse.py:37:12-37: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:236:41-86: Missing argument `realf` [missing-argument]
+ ERROR lib/spack/spack/traverse.py:236:42-48: Unexpected keyword argument `parent` [unexpected-keyword]
+ ERROR lib/spack/spack/traverse.py:236:55-59: Unexpected keyword argument `spec` [unexpected-keyword]
+ ERROR lib/spack/spack/traverse.py:236:63-70: Unexpected keyword argument `depflag` [unexpected-keyword]
+ ERROR lib/spack/spack/traverse.py:236:74-82: Unexpected keyword argument `virtuals` [unexpected-keyword]
+ ERROR lib/spack/spack/traverse.py:460:16-41: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:475:27-52: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:490:22-47: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:490:62-87: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:503:22-47: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:503:62-87: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/traverse.py:665:27-52: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
- ERROR lib/spack/spack/util/path.py:76:34-52: Object of class `NoneType` has no attribute `family` [missing-attribute]
- ERROR lib/spack/spack/variant.py:511:55-62: Argument `tuple[None]` is not assignable to parameter `value` with type `tuple[bool | str, ...]` in function `VariantValue.__init__` [bad-argument-type]
+ ERROR lib/spack/spack/variant.py:159:41-53: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/variant.py:216:32-44: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/variant.py:218:19-51: Object of class `FunctionType` has no attribute `from_string_or_bool` [missing-attribute]
+ ERROR lib/spack/spack/variant.py:222:58-70: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/variant.py:224:48-57: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:312:11-23: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/variant.py:316:36-40: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:316:56-65: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:316:77-85: Unexpected keyword argument `concrete` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:322:35-39: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:322:74-83: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:325:49-53: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:325:65-74: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:330:11-23: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/variant.py:332:51-55: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:332:67-76: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:336:35-39: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:336:69-78: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:340:52-56: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:340:62-71: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:344:13-17: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:346:13-22: Unexpected keyword argument `propagate` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:347:13-21: Unexpected keyword argument `concrete` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:351:64-76: Expected a type form, got instance of `((realf: Unknown) -> Unknown) | Unknown` [not-a-type]
+ ERROR lib/spack/spack/variant.py:354:51-55: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:356:52-56: Expected 1 positional argument, got 3 [bad-argument-count]
+ ERROR lib/spack/spack/variant.py:356:68-76: Unexpected keyword argument `concrete` [unexpected-keyword]
+ ERROR lib/spack/spack/variant.py:358:53-57: Expected 1 positional argument, got 3 [bad-argument-count]

... (truncated 15 lines) ...

spark (https://github.com/apache/spark)
+ ERROR python/pyspark/testing/tests/test_skip_class.py:20:1-15: Argument `type[SkipClassTests]` is not assignable to parameter `reason` with type `str` in function `unittest.case.skip` [bad-argument-type]

jax (https://github.com/google/jax)
+ ERROR jax/experimental/array_serialization/serialization_test.py:961:5-35: `UserPytreeAPITest.test_custom_node_registration.P` is not assignable to upper bound `Hashable` of type variable `H` [bad-specialization]

@github-actions
Copy link

Primer Diff Classification

❌ 5 regression(s) | ✅ 4 improvement(s) | 9 project(s) total | +185, -34 errors

5 regression(s) across scikit-learn, steam.py, core, spack, jax. error kinds: invalid-inheritance, missing-attribute, bad-return. 4 improvement(s) across pandera, pandas, sphinx, spark.

Project Verdict Changes Error Kinds Root Cause
pandera ✅ Improvement -2 bad-argument-type The changes to class name resolution in solve.rs, parti...
scikit-learn ❌ Regression +1, -1 invalid-inheritance, missing-attribute pyrefly/lib/alt/solve.rs
steam.py ❌ Regression +2 non-convergent-recursion pyrefly/lib/alt/solve.rs
pandas ✅ Improvement -4 bad-argument-type pyrefly/lib/alt/solve.rs
core ❌ Regression +1 bad-return pyrefly/lib/alt/solve.rs
sphinx ✅ Improvement -1 unnecessary-comparison pyrefly/lib/alt/solve.rs
spack ❌ Regression +179, -26 bad-argument-count, bad-argument-type pyrefly/lib/alt/solve.rs
spark ✅ Improvement +1 bad-argument-type pyrefly/lib/alt/solve.rs
jax ❌ Regression +1 bad-specialization pyrefly/lib/alt/solve.rs
Detailed analysis

❌ Regression (5)

scikit-learn (+1, -1)

This is a regression. The new error is a false positive - @deprecated returns a class that can be inherited from, but pyrefly now sees it as a non-class type. The removed error was a true positive - PassiveAggressiveRegressor really doesn't have transform(), and pyrefly should catch this. The PR made class decorator handling too aggressive, transforming class types into non-class types even when the decorator preserves the class interface.
Attribution: The changes in pyrefly/lib/alt/solve.rs now apply class decorators to the runtime class-value binding. The logic at lines 4813-4854 transforms the class type through each decorator, potentially changing it from a class type to a callable type if the decorator returns a callable.

steam.py (+2)

These are false positive warnings - pyrefly's type inference is failing to converge on generic classes with circular type parameter dependencies (Channel references Message, Message references Channel). The code is correct and other type checkers handle it fine. This is a regression in pyrefly's inference capabilities introduced by the class decorator changes.
Attribution: The changes to Binding::ClassDef handling in pyrefly/lib/alt/solve.rs that now processes decorators appears to have affected type inference for all class definitions, even those without decorators

core (+1)

The new class decorator logic is incorrectly changing the type identity of classes decorated with @deprecated_class. The decorator likely returns a wrapper or the original class, but pyrefly is now treating it as a different type, breaking the expected inheritance relationship where SelectedEntities (the local subclass) should be assignable to itself
Attribution: The change to class decorator handling in pyrefly/lib/alt/solve.rs lines 4810-4855, which now applies decorators to determine the runtime type of decorated classes

spack (+179, -26)

This is a regression. The PR introduces new type inference failures when dealing with decorated classes. The errors show pyrefly is now unable to properly track types through decorator applications, resulting in Unknown types and cascading errors. Key evidence: (1) 174/179 errors are pyrefly-only, (2) errors involve Unknown types and FunctionType where class types should be, (3) the pattern suggests pyrefly loses track of the decorated class's type when it's imported or accessed through module boundaries. While the PR correctly removes some false positives (26 errors), it introduces 179 new false positives, making it a net regression.
Attribution: The changes in pyrefly/lib/alt/solve.rs lines 4810-4855 implement class decorator application. The new logic applies decorators to class values, which appears to be causing type inference issues when decorated classes are imported or used in complex ways.

jax (+1)

This is a regression. The PR introduced support for analyzing class decorators, but it's incorrectly flagging a valid JAX pattern. The @jax.tree_util.register_static decorator is a standard way to register static pytree nodes in JAX, and the fact that neither mypy nor pyright flag this indicates pyrefly is applying an overly strict interpretation. The decorator works fine at runtime and is used throughout the JAX ecosystem.
Attribution: The change to class decorator handling in pyrefly/lib/alt/solve.rs now processes decorators and validates their type constraints, whereas previously decorators were ignored for type-level analysis. Specifically, the code starting at line 4813 now iterates through decorators and applies them to the class type.

✅ Improvement (4)

pandera (-2)

These were false positive errors. The code correctly adds numpy/pandas dtype classes to a set of unsupported dtypes. The PR's improvements to class decorator handling and name resolution fixed pyrefly's ability to properly infer the types of these class objects when used as values (not type annotations). Since pyrefly was wrongly flagging correct code and now accepts it, this is an improvement.
Attribution: The changes to class name resolution in solve.rs, particularly the new handling of Expr::Name for ClassDef bindings, fixed the type inference for these class objects when used as values in set operations

pandas (-4)

These removals represent an improvement. The errors were false positives caused by poor type inference (evidenced by Unknown types in the error messages). The PR's implementation of class decorator support appears to have improved type inference, allowing pyrefly to correctly understand that these assignments are valid. The test file errors about set[str] were also false positives - pandas legitimately accepts sets as index/columns arguments and converts them internally.
Attribution: The changes in pyrefly/lib/alt/solve.rs that now apply class decorators to runtime class values likely improved type inference, resolving the Unknown type issues that were causing these false positive errors.

sphinx (-1)

This is an improvement. The removed error was a false positive - pyrefly was incorrectly claiming that an identity comparison between allowed_redirects and the sentinel class _SENTINEL_LAR would always be False. This is a standard Python sentinel pattern where a unique object (in this case a class decorated with @object.__new__) is used as a default value and checked with is. The PR's changes to preserve class types through decorators fixed this inference issue, allowing pyrefly to correctly understand that _SENTINEL_LAR can indeed be compared with is.
Attribution: The changes to class decorator handling in pyrefly/lib/alt/solve.rs (specifically the new logic in Binding::ClassDef that preserves the original class type when decorators are applied) fixed the false positive by correctly recognizing that _SENTINEL_LAR remains a valid class object despite the @object.__new__ decorator.

spark (+1)

This is an improvement. The error correctly identifies a type incompatibility - when @unittest.skip is used without parentheses, it receives the class directly, but the type stub apparently only defines the version that takes a reason string. While this doesn't cause runtime errors (unittest handles both cases), it's a genuine type-level issue that pyrefly is now catching thanks to improved decorator analysis. The fact that pyright also reports this error supports that this is a real type issue, not a pyrefly bug.
Attribution: The changes to class decorator handling in pyrefly/lib/alt/solve.rs, specifically the new code in the Binding::ClassDef case that now properly applies decorators and checks their types, caused this error to appear. The decorator is now being type-checked when applied to the class.

Suggested fixes

Summary: Class decorator handling in solve.rs is too aggressive, transforming class types into non-class types even when decorators preserve the class interface, causing 183 pyrefly-only false positives across 5 projects.

1. In the Binding::ClassDef case in solve.rs (lines 4810-4855), add a type preservation check: after applying each decorator, if the resulting type is not a class type but the decorator is known to preserve classes (like @deprecated, @deprecated_class, @jax.tree_util.register_static), keep the original class type instead of the transformed type

Files: pyrefly/lib/alt/solve.rs
Confidence: high
Affected projects: scikit-learn, steam.py, core, spack, jax
Fixes: bad-override, bad-assignment, incompatible-argument, type-mismatch
The current logic blindly applies decorator transformations even when decorators are class-preserving wrappers. By checking if common class-preserving decorators are used and maintaining the class type in those cases, we eliminate the false positives where pyrefly thinks decorated classes are no longer classes. Expected outcome: eliminates 183 errors across scikit-learn, steam.py, core, spack, and jax

2. In the Binding::ClassDef case in solve.rs, add a fallback: if untype_opt returns Some (indicating the decorator returned a non-type), but the decorator is applied to a class in a non-interface module, preserve the original class type binding for type-level usage while keeping the decorator result for runtime usage

Files: pyrefly/lib/alt/solve.rs
Confidence: medium
Affected projects: spack
Fixes: type-mismatch, incompatible-argument
The untype_opt check at line 4848 correctly identifies when a decorator returns a non-type, but the fallback of just using the original class type is insufficient. We need to maintain dual bindings - the decorated result for runtime calls and the original class for type-level operations like inheritance. This would fix the spack regression where decorated classes become Unknown when imported


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (9 LLM)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preliminary support for class decorators

2 participants