Skip to content

Commit

Permalink
New semantic analyzer: fix crash related to dataclasses.InitVar (pyth…
Browse files Browse the repository at this point in the history
…on#6984)

Previously InitVar attributes were missing on the second semantic
analysis pass, since the dataclasses plugin removes those attributes
from the class symbol table. This caused a crash. The fix is to reset
the attribute declarations so that they will be re-added to the symbol
table on successive semantic analysis passes.

Fixes python#6955.
  • Loading branch information
JukkaL authored and PattenR committed Jun 23, 2019
1 parent 153f8aa commit 4e5f696
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 4 deletions.
20 changes: 16 additions & 4 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Plugin that provides support for dataclasses."""

from collections import OrderedDict
from typing import Dict, List, Set, Tuple

Expand Down Expand Up @@ -166,16 +168,26 @@ def transform(self) -> None:
if decorator_arguments['frozen']:
self._freeze(attributes)

# Remove init-only vars from the class.
for attr in attributes:
if attr.is_init_var:
del info.names[attr.name]
self.reset_init_only_vars(info, attributes)

info.metadata['dataclass'] = {
'attributes': OrderedDict((attr.name, attr.serialize()) for attr in attributes),
'frozen': decorator_arguments['frozen'],
}

def reset_init_only_vars(self, info: TypeInfo, attributes: List[DataclassAttribute]) -> None:
"""Remove init-only vars from the class and reset init var declarations."""
for attr in attributes:
if attr.is_init_var:
del info.names[attr.name]
for stmt in info.defn.defs.body:
if isinstance(stmt, AssignmentStmt) and stmt.unanalyzed_type:
lvalue = stmt.lvalues[0]
if isinstance(lvalue, NameExpr) and lvalue.name == attr.name:
# Reset node so that another semantic analysis pass will
# recreate a symbol node for this attribute.
lvalue.node = None

def collect_attributes(self) -> List[DataclassAttribute]:
"""Collect all attributes declared in the dataclass and its parents.
Expand Down
20 changes: 20 additions & 0 deletions test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,26 @@ app.database_name # E: "SpecializedApplication" has no attribute "database_name

[builtins fixtures/list.pyi]

[case testDataclassesInitVarsAndDefer]
# flags: --new-semantic-analyzer
from dataclasses import InitVar, dataclass

defer: Yes

@dataclass
class Application:
name: str
database_name: InitVar[str]

reveal_type(Application) # E: Revealed type is 'def (name: builtins.str, database_name: builtins.str) -> __main__.Application'
app = Application("example", 42) # E: Argument 2 to "Application" has incompatible type "int"; expected "str"
app = Application("example", "apps")
app.name
app.database_name # E: "Application" has no attribute "database_name"

class Yes: ...
[builtins fixtures/list.pyi]

[case testDataclassFactory]
from typing import Type, TypeVar
from dataclasses import dataclass
Expand Down

0 comments on commit 4e5f696

Please sign in to comment.