Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add alias support to field() in attrs plugin #16610

Merged
merged 1 commit into from Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 20 additions & 3 deletions mypy/plugins/attrs.py
Expand Up @@ -105,6 +105,7 @@ class Attribute:
def __init__(
self,
name: str,
alias: str | None,
info: TypeInfo,
has_default: bool,
init: bool,
Expand All @@ -114,6 +115,7 @@ def __init__(
init_type: Type | None,
) -> None:
self.name = name
self.alias = alias
self.info = info
self.has_default = has_default
self.init = init
Expand Down Expand Up @@ -171,12 +173,14 @@ def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument:
arg_kind = ARG_OPT if self.has_default else ARG_POS

# Attrs removes leading underscores when creating the __init__ arguments.
return Argument(Var(self.name.lstrip("_"), init_type), init_type, None, arg_kind)
name = self.alias or self.name.lstrip("_")
return Argument(Var(name, init_type), init_type, None, arg_kind)

def serialize(self) -> JsonDict:
"""Serialize this object so it can be saved and restored."""
return {
"name": self.name,
"alias": self.alias,
"has_default": self.has_default,
"init": self.init,
"kw_only": self.kw_only,
Expand Down Expand Up @@ -205,6 +209,7 @@ def deserialize(

return Attribute(
data["name"],
data["alias"],
info,
data["has_default"],
data["init"],
Expand Down Expand Up @@ -498,6 +503,7 @@ def _attributes_from_assignment(
or if auto_attribs is enabled also like this:
x: type
x: type = default_value
x: type = attr.ib(...)
"""
for lvalue in stmt.lvalues:
lvalues, rvalues = _parse_assignments(lvalue, stmt)
Expand Down Expand Up @@ -564,7 +570,7 @@ def _attribute_from_auto_attrib(
has_rhs = not isinstance(rvalue, TempNode)
sym = ctx.cls.info.names.get(name)
init_type = sym.type if sym else None
return Attribute(name, ctx.cls.info, has_rhs, True, kw_only, None, stmt, init_type)
return Attribute(name, None, ctx.cls.info, has_rhs, True, kw_only, None, stmt, init_type)


def _attribute_from_attrib_maker(
Expand Down Expand Up @@ -628,9 +634,20 @@ def _attribute_from_attrib_maker(
converter = convert
converter_info = _parse_converter(ctx, converter)

# Custom alias might be defined:
alias = None
alias_expr = _get_argument(rvalue, "alias")
if alias_expr:
alias = ctx.api.parse_str_literal(alias_expr)
if alias is None:
ctx.api.fail(
'"alias" argument to attrs field must be a string literal',
rvalue,
code=LITERAL_REQ,
)
name = unmangle(lhs.name)
return Attribute(
name, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type
name, alias, ctx.cls.info, attr_has_default, init, kw_only, converter_info, stmt, init_type
Copy link
Contributor

Choose a reason for hiding this comment

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

One day we'll make it kw-only :)

)


Expand Down
25 changes: 25 additions & 0 deletions test-data/unit/check-plugin-attrs.test
Expand Up @@ -1055,6 +1055,31 @@ C()
C(_x=42) # E: Unexpected keyword argument "_x" for "C"
[builtins fixtures/list.pyi]

[case testAttrsAliasForInit]
from attrs import define, field

@define
class C1:
_x: int = field(alias="x1")

c1 = C1(x1=42)
reveal_type(c1._x) # N: Revealed type is "builtins.int"
c1.x1 # E: "C1" has no attribute "x1"
C1(_x=42) # E: Unexpected keyword argument "_x" for "C1"

alias = "x2"
@define
class C2:
_x: int = field(alias=alias) # E: "alias" argument to attrs field must be a string literal

@define
class C3:
_x: int = field(alias="_x")

c3 = C3(_x=1)
reveal_type(c3._x) # N: Revealed type is "builtins.int"
[builtins fixtures/plugin_attrs.pyi]

[case testAttrsAutoMustBeAll]
import attr
@attr.s(auto_attribs=True)
Expand Down
4 changes: 4 additions & 0 deletions test-data/unit/lib-stub/attrs/__init__.pyi
Expand Up @@ -79,6 +79,7 @@ def field(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> Any: ...

# This form catches an explicit None or no default and infers the type from the
Expand All @@ -98,6 +99,7 @@ def field(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[object] = ...,
alias: Optional[str] = ...,
) -> _T: ...

# This form catches an explicit default argument.
Expand All @@ -116,6 +118,7 @@ def field(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[object] = ...,
alias: Optional[str] = ...,
) -> _T: ...

# This form covers type=non-Type: e.g. forward references (str), Any
Expand All @@ -134,6 +137,7 @@ def field(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[object] = ...,
alias: Optional[str] = ...,
) -> Any: ...

def evolve(inst: _T, **changes: Any) -> _T: ...
Expand Down