Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix Feature: support @field.default decorator for attrs #2012#2494

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

fix Feature: support @field.default decorator for attrs #2012#2494
asukaminato0721 wants to merge 1 commit intofacebook:mainfrom
asukaminato0721:2012

Conversation

@asukaminato0721
Copy link
Contributor

@asukaminato0721 asukaminato0721 commented Feb 23, 2026

Summary

Fixes #2012

Recognize @<field>.default on attrs field specifiers in class bodies, suppress the bogus attribute error, and mark the decorated method with an attrs-default flag so it’s treated as a special decorator.

Use that flag when building dataclass/attrs field metadata to treat the field as having a default (so __init__ no longer requires it).

Test Plan

Updated the attrs test to remove the bug marker and expected errors.

@meta-cla meta-cla bot added the cla signed label Feb 23, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 23, 2026 00:33
Copilot AI review requested due to automatic review settings February 23, 2026 00:33
@asukaminato0721 asukaminato0721 marked this pull request as draft February 23, 2026 00:33
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

Adds support in Pyrefly’s attrs handling for the @<field>.default decorator pattern so that defaults provided via attrs’ decorator API are reflected in synthesized __init__ signatures (fixes #2012).

Changes:

  • Detect @<field>.default decorators in the binder and propagate the target field name through decorator metadata.
  • Apply the decorated method’s return type as the field default for attrs classes when no explicit default is present.
  • Update attrs test coverage to expect no errors for the @a.default default-factory pattern.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyrefly/lib/test/attrs/fields.rs Updates attrs tests to validate @field.default behavior (no error + __init__ no longer requires the field arg).
pyrefly/lib/binding/expr.rs Detects @<field>.default in class scope and attaches attrs_default_field metadata to decorator bindings.
pyrefly/lib/binding/binding.rs Extends BindingDecorator to carry attrs_default_field through the binding layer.
pyrefly/lib/alt/types/decorated_function.rs Extends decorator metadata/types to track attrs default-field linkage and introduces a SpecialDecorator case.
pyrefly/lib/alt/traits.rs Initializes the new decorator metadata field when promoting recursive decorator answers.
pyrefly/lib/alt/special_calls.rs Ensures synthesized Decorator instances default attrs_default_field to None.
pyrefly/lib/alt/solve.rs Suppresses decorator-expression errors for recognized attrs default decorators and propagates metadata into Decorator.
pyrefly/lib/alt/function.rs Treats attrs default decorators as a special decorator affecting function flags.
pyrefly/lib/alt/class/class_field.rs Uses @<field>.default method return types as defaults for attrs fields during dataclass/attrs field processing.
crates/pyrefly_types/src/types.rs Exposes Type::attrs_default_field() accessor to query function metadata.
crates/pyrefly_types/src/callable.rs Adds attrs_default_field to FuncFlags to persist this metadata in callable/function types.

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

Comment on lines +1131 to +1149
fn is_attrs_field_specifier(&self, expr: &Expr) -> bool {
let Expr::Call(call) = expr else {
return false;
};
match &*call.func {
Expr::Name(name) => matches!(name.id.as_str(), "field" | "attrib" | "ib"),
Expr::Attribute(attr) => {
let attr_name = attr.attr.id.as_str();
if !matches!(attr_name, "field" | "attrib" | "ib") {
return false;
}
if let Expr::Name(base) = &*attr.value {
matches!(base.id.as_str(), "attr" | "attrs")
} else {
false
}
}
_ => false,
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

is_attrs_field_specifier currently treats any call to a function named field/attrib/ib as an attrs field specifier, without verifying that the callee actually comes from the attr/attrs modules. This can incorrectly mark unrelated field() usages as attrs fields, which then (a) suppresses decorator type errors via error_swallower() and (b) sets attrs_default_field metadata unexpectedly. Consider checking the binding for the callee (e.g., FlowStyle::Import/ImportAs with ModuleName::attr()/ModuleName::attrs()) to avoid false positives, similar to how attrs classes are detected in class metadata.

Copilot uses AI. Check for mistakes.
Comment on lines +2409 to +2430
fn attrs_default_for_field(&self, cls: &Class, field_name: &Name) -> Option<Type> {
for name in cls.class_body_fields() {
if name == field_name {
continue;
}
let Some(field) = self.get_non_synthesized_field_from_current_class_only(cls, name)
else {
continue;
};
if !matches!(&field.0, ClassFieldInner::Method { .. }) {
continue;
}
let ty = field.ty();
if ty
.attrs_default_field()
.is_some_and(|default_name| default_name == field_name)
{
return ty
.callable_return_type(self.heap)
.or_else(|| Some(self.heap.mk_any_implicit()));
}
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

attrs_default_for_field linearly scans all class_body_fields() and runs several lookups to find a matching @<field>.default method. Since get_dataclass_member is called in loops over all fields (e.g. dataclass synthesis/validation), this introduces an O(n^2) walk for attrs classes with many fields. Consider precomputing a map of {field_name -> default_return_type} once per class (e.g. during class metadata/field synthesis) or otherwise caching the result to avoid repeated scans.

Copilot uses AI. Check for mistakes.
Comment on lines 10 to 24
@@ -17,11 +16,11 @@ from attrs import define, field
class C:
a: dict = field()

@a.default # E: Object of class `dict` has no attribute `default`
@a.default
def _default_a(self):
return {}

c = C() # E: Missing argument `a` in function `C.__init__`
c = C()
"#,
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The new @field.default support is only covered by the from attrs import field form. Since the implementation also has branches for attr.ib/attr.attrib/attrs.field-style calls (and should avoid false positives when field() is not from attrs), it would be good to add tests for at least one import attr; x = attr.ib() case and one negative case where a user-defined field() is used and @x.default should still error.

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

This comment has been minimized.

@yangdanny97 yangdanny97 requested a review from migeed-z February 26, 2026 14:52
@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 26, 2026 23:14
@github-actions
Copy link

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

core (https://github.com/home-assistant/core)
- ERROR homeassistant/helpers/entity_registry.py:220:6-20: Object of class `str` has no attribute `default` [missing-attribute]
- ERROR homeassistant/helpers/entity_registry.py:510:6-20: Object of class `str` has no attribute `default` [missing-attribute]

@github-actions
Copy link

Primer Diff Classification

✅ 1 improvement(s) | 1 project(s) total

1 improvement(s) across core.

Project Verdict Changes Error Kinds Root Cause
core ✅ Improvement -2 missing-attribute attrs_default_decorator_field()
Detailed analysis

✅ Improvement (1)

core (-2)

The removed errors were false positives. In attrs classes, fields defined with attr.ib() or field() are not plain strings but field specifiers that have a default attribute which can be used as a decorator (e.g., @domain.default). The PR correctly implemented support for this attrs-specific pattern by recognizing when a decorator follows the @<field_name>.default pattern within an attrs class context. Lines 220 and 510 show valid usage: domain: str = attr.ib(init=False, repr=False) creates an attrs field, and @domain.default is the standard way to provide a default value factory for that field. Pyrefly now correctly understands this pattern instead of incorrectly treating domain as a plain str type.
Attribution: The change to attrs_default_decorator_field() in pyrefly/lib/binding/expr.rs added logic to recognize @<field>.default decorators in attrs classes. The is_attrs_field_specifier() function identifies when a name refers to an attrs field, and the BindingDecorator struct was extended with an attrs_default_field field to track this information. This allows pyrefly to suppress the bogus attribute error and properly handle attrs default decorators.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (1 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.

Feature: support @field.default decorator for attrs

3 participants