Repository: python/mypy

Issue:
mypy generates `__match_args__` for dataclasses and `NamedTuple`s on Python <3.10
**Bug Report**

Not a big deal, but mypy appears to generate `__match_args__` for dataclasses and `NamedTuple`s even when running with `--python-version` set to 3.9:

```python
from dataclasses import dataclass
from typing import NamedTuple

@dataclass
class One:
    bar: int

f: One
reveal_type(f.__match_args__)  # Revealed type is "Tuple[Literal['bar']]"

class Two(NamedTuple):
    baz: str

g: Two
reveal_type(g.__match_args__)  # Revealed type is "Tuple[Literal['baz']]"
```

**Expected Behavior**

Mypy should not autogenerate `__match_args__` when running with `--python-version` set to <3.10, as pattern-matching is new in 3.10.

**Your Environment**

- Mypy version used: `0.950+dev.a33d2353a81d075850a487f3913720037f2473aa (compiled: no)`
- Mypy command-line flags: `--python-version 3.9`
- Mypy configuration options from `mypy.ini` (and other config files): None
- Python version used: 3.10
- Operating system and version: Windows



Discussion:
This is probably a good issue for someone new to mypy.

Resolving patch:
diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py
--- a/mypy/plugins/dataclasses.py
+++ b/mypy/plugins/dataclasses.py
@@ -230,7 +230,8 @@ def transform(self) -> bool:
         if (decorator_arguments['match_args'] and
                 ('__match_args__' not in info.names or
                  info.names['__match_args__'].plugin_generated) and
-                attributes):
+                attributes and
+                py_version >= (3, 10)):
             str_type = ctx.api.named_type("builtins.str")
             literals: List[Type] = [LiteralType(attr.name, str_type)
                         for attr in attributes if attr.is_in_init]
diff --git a/mypy/semanal_namedtuple.py b/mypy/semanal_namedtuple.py
--- a/mypy/semanal_namedtuple.py
+++ b/mypy/semanal_namedtuple.py
@@ -447,7 +447,8 @@ def add_field(var: Var, is_initialized_in_class: bool = False,
         add_field(Var('_source', strtype), is_initialized_in_class=True)
         add_field(Var('__annotations__', ordereddictype), is_initialized_in_class=True)
         add_field(Var('__doc__', strtype), is_initialized_in_class=True)
-        add_field(Var('__match_args__', match_args_type), is_initialized_in_class=True)
+        if self.options.python_version >= (3, 10):
+            add_field(Var('__match_args__', match_args_type), is_initialized_in_class=True)
 
         tvd = TypeVarType(SELF_TVAR_NAME, info.fullname + '.' + SELF_TVAR_NAME,
                          -1, [], info.tuple_type)


Test patch:
diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py
--- a/mypy/test/testmerge.py
+++ b/mypy/test/testmerge.py
@@ -7,7 +7,6 @@
 from mypy import build
 from mypy.build import BuildResult
 from mypy.modulefinder import BuildSource
-from mypy.defaults import PYTHON3_VERSION
 from mypy.errors import CompileError
 from mypy.nodes import (
     Node, MypyFile, SymbolTable, SymbolTableNode, TypeInfo, Expression, Var, TypeVarExpr,
@@ -107,7 +106,6 @@ def build(self, source: str, testcase: DataDrivenTestCase) -> Optional[BuildResu
         options.use_builtins_fixtures = True
         options.export_types = True
         options.show_traceback = True
-        options.python_version = PYTHON3_VERSION
         main_path = os.path.join(test_temp_dir, 'main')
         with open(main_path, 'w', encoding='utf8') as f:
             f.write(source)
diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test
--- a/test-data/unit/check-dataclasses.test
+++ b/test-data/unit/check-dataclasses.test
@@ -1790,3 +1790,48 @@ class MyDataclass:
 class MyGeneric(Generic[T]): ...
 class MyClass(MyGeneric[MyDataclass]): ...
 [builtins fixtures/dataclasses.pyi]
+
+[case testDataclassWithMatchArgs]
+# flags: --python-version 3.10
+from dataclasses import dataclass
+@dataclass
+class One:
+    bar: int
+    baz: str
+o: One
+reveal_type(o.__match_args__)  # N: Revealed type is "Tuple[Literal['bar'], Literal['baz']]"
+@dataclass(match_args=True)
+class Two:
+    bar: int
+t: Two
+reveal_type(t.__match_args__)  # N: Revealed type is "Tuple[Literal['bar']]"
+[builtins fixtures/dataclasses.pyi]
+
+[case testDataclassWithoutMatchArgs]
+# flags: --python-version 3.10
+from dataclasses import dataclass
+@dataclass(match_args=False)
+class One:
+    bar: int
+    baz: str
+o: One
+reveal_type(o.__match_args__)  # E: "One" has no attribute "__match_args__" \
+                               # N: Revealed type is "Any"
+[builtins fixtures/dataclasses.pyi]
+
+[case testDataclassWithMatchArgsOldVersion]
+# flags: --python-version 3.9
+from dataclasses import dataclass
+@dataclass(match_args=True)
+class One:
+    bar: int
+o: One
+reveal_type(o.__match_args__)  # E: "One" has no attribute "__match_args__" \
+                               # N: Revealed type is "Any"
+@dataclass
+class Two:
+    bar: int
+t: Two
+reveal_type(t.__match_args__)  # E: "Two" has no attribute "__match_args__" \
+                               # N: Revealed type is "Any"
+[builtins fixtures/dataclasses.pyi]
diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test
--- a/test-data/unit/check-namedtuple.test
+++ b/test-data/unit/check-namedtuple.test
@@ -1134,3 +1134,26 @@ def f(fields) -> None:
     NT2 = namedtuple("bad", "x") # E: First argument to namedtuple() should be "NT2", not "bad"
     nt2: NT2 = NT2(x=1)
 [builtins fixtures/tuple.pyi]
+
+[case testNamedTupleHasMatchArgs]
+# flags: --python-version 3.10
+from typing import NamedTuple
+class One(NamedTuple):
+    bar: int
+    baz: str
+o: One
+reveal_type(o.__match_args__)  # N: Revealed type is "Tuple[Literal['bar'], Literal['baz']]"
+[builtins fixtures/tuple.pyi]
+[typing fixtures/typing-namedtuple.pyi]
+
+[case testNamedTupleHasNoMatchArgsOldVersion]
+# flags: --python-version 3.9
+from typing import NamedTuple
+class One(NamedTuple):
+    bar: int
+    baz: str
+o: One
+reveal_type(o.__match_args__)  # E: "One" has no attribute "__match_args__" \
+                               # N: Revealed type is "Any"
+[builtins fixtures/tuple.pyi]
+[typing fixtures/typing-namedtuple.pyi]
diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test
--- a/test-data/unit/deps.test
+++ b/test-data/unit/deps.test
@@ -1417,7 +1417,7 @@ class D(C):
 <m.C> -> m, m.C, m.D
 <m.D> -> m.D
 
-[case testDataclassDeps]
+[case testDataclassDepsOldVersion]
 # flags: --python-version 3.7
 from dataclasses import dataclass
 
@@ -1432,6 +1432,36 @@ class B(A):
     y: int
 [builtins fixtures/dataclasses.pyi]
 
+[out]
+<m.A.(abstract)> -> <m.B.__init__>, m
+<m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
+<m.A.__init__> -> <m.B.__init__>, m.B.__init__
+<m.A.__new__> -> <m.B.__new__>
+<m.A.x> -> <m.B.x>
+<m.A.y> -> <m.B.y>
+<m.A> -> m, m.A, m.B
+<m.A[wildcard]> -> m
+<m.B.y> -> m
+<m.B> -> m.B
+<m.Z> -> m
+<dataclasses.dataclass> -> m
+<dataclasses> -> m
+
+[case testDataclassDeps]
+# flags: --python-version 3.10
+from dataclasses import dataclass
+
+Z = int
+
+@dataclass
+class A:
+    x: Z
+
+@dataclass
+class B(A):
+    y: int
+[builtins fixtures/dataclasses.pyi]
+
 [out]
 <m.A.(abstract)> -> <m.B.__init__>, m
 <m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test
--- a/test-data/unit/merge.test
+++ b/test-data/unit/merge.test
@@ -646,7 +646,7 @@ TypeInfo<2>(
     f<3>))
 
 [case testNamedTuple_typeinfo]
-
+# flags: --python-version 3.10
 import target
 [file target.py]
 from typing import NamedTuple
@@ -707,6 +707,65 @@ TypeInfo<2>(
     x<19> (target.A<0>)
     y<20> (target.A<0>)))
 
+[case testNamedTupleOldVersion_typeinfo]
+import target
+[file target.py]
+from typing import NamedTuple
+class A: pass
+N = NamedTuple('N', [('x', A)])
+[file target.py.next]
+from typing import NamedTuple
+class A: pass
+N = NamedTuple('N', [('x', A), ('y', A)])
+[builtins fixtures/tuple.pyi]
+[out]
+TypeInfo<0>(
+  Name(target.A)
+  Bases(builtins.object<1>)
+  Mro(target.A<0>, builtins.object<1>)
+  Names())
+TypeInfo<2>(
+  Name(target.N)
+  Bases(builtins.tuple[target.A<0>, ...]<3>)
+  Mro(target.N<2>, builtins.tuple<3>, typing.Sequence<4>, typing.Iterable<5>, builtins.object<1>)
+  Names(
+    _NT<6>
+    __annotations__<7> (builtins.object<1>)
+    __doc__<8> (builtins.str<9>)
+    __new__<10>
+    _asdict<11>
+    _field_defaults<12> (builtins.object<1>)
+    _field_types<13> (builtins.object<1>)
+    _fields<14> (Tuple[builtins.str<9>])
+    _make<15>
+    _replace<16>
+    _source<17> (builtins.str<9>)
+    x<18> (target.A<0>)))
+==>
+TypeInfo<0>(
+  Name(target.A)
+  Bases(builtins.object<1>)
+  Mro(target.A<0>, builtins.object<1>)
+  Names())
+TypeInfo<2>(
+  Name(target.N)
+  Bases(builtins.tuple[target.A<0>, ...]<3>)
+  Mro(target.N<2>, builtins.tuple<3>, typing.Sequence<4>, typing.Iterable<5>, builtins.object<1>)
+  Names(
+    _NT<6>
+    __annotations__<7> (builtins.object<1>)
+    __doc__<8> (builtins.str<9>)
+    __new__<10>
+    _asdict<11>
+    _field_defaults<12> (builtins.object<1>)
+    _field_types<13> (builtins.object<1>)
+    _fields<14> (Tuple[builtins.str<9>, builtins.str<9>])
+    _make<15>
+    _replace<16>
+    _source<17> (builtins.str<9>)
+    x<18> (target.A<0>)
+    y<19> (target.A<0>)))
+
 [case testUnionType_types]
 import target
 [file target.py]