Repository: pydantic/pydantic

Issue:
Allow inheriting `dataclasses.dataclass` with defaults
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

Currently, inheriting a pure dataclass to a subclass that is also a `BaseModel` works. This is great, and this feature has been used extensively in v1 in a code base i maintain. v2 seems to have the added feature of not requiring to re-declare the fields, which is awesome.

However, if the dataclass defines a default for a field, it crashes with a `NameError`

```console
Traceback (most recent call last):
...
  File "<input>", line 19, in <module>
  File ".../lib/python3.10/site-packages/pydantic/_internal/_model_construction.py", line 172, in __new__
    set_model_fields(cls, bases, config_wrapper, types_namespace)
  File ".../lib/python3.10/site-packages/pydantic/_internal/_model_construction.py", line 400, in set_model_fields
    fields, class_vars = collect_model_fields(cls, bases, config_wrapper, types_namespace, typevars_map=typevars_map)
  File ".../lib/python3.10/site-packages/pydantic/_internal/_fields.py", line 155, in collect_model_fields
    raise NameError(
NameError: Field name "a" shadows an attribute in parent "A"; you might want to use a different field name with "alias='a'".
```

### Example Code

```Python
import pydantic

from dataclasses import dataclass

import pydantic


@dataclass
class A:
    a: None

class B(pydantic.BaseModel, A): # Ok
    pass


@dataclass
class A:
    a: None = None
    

class B(pydantic.BaseModel, A): # Not ok
    pass
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.1.1
        pydantic-core version: 2.4.0
          pydantic-core build: profile=release pgo=true mimalloc=true
                 install path: .../lib/python3.10/site-packages/pydantic
               python version: 3.10.2 (main, Mar  3 2022, 12:03:29) [GCC 9.3.0]
                     platform: Linux-5.14.0-1054-oem-x86_64-with-glibc2.35
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @lig


Discussion:
@tobni thank you for the bug report.

I can confirm that the issue reproduces in Pydantic v2.1.1.

Resolving patch:
diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py
--- a/pydantic/_internal/_fields.py
+++ b/pydantic/_internal/_fields.py
@@ -147,10 +147,18 @@ def collect_model_fields(  # noqa: C901
         # "... shadows an attribute" errors
         generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin')
         for base in bases:
+            dataclass_fields = {
+                field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ())
+            }
             if hasattr(base, ann_name):
                 if base is generic_origin:
                     # Don't error when "shadowing" of attributes in parametrized generics
                     continue
+
+                if ann_name in dataclass_fields:
+                    # Don't error when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set
+                    # on the class instance.
+                    continue
                 warnings.warn(
                     f'Field name "{ann_name}" shadows an attribute in parent "{base.__qualname__}"; ',
                     UserWarning,


Test patch:
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -2500,3 +2500,50 @@ class StdLibDataclass:
 
     assert is_pydantic_dataclass(PydanticDataclass) is True
     assert is_pydantic_dataclass(StdLibDataclass) is False
+
+
+def test_can_inherit_stdlib_dataclasses_with_defaults():
+    @dataclasses.dataclass
+    class Base:
+        a: None = None
+
+    class Model(BaseModel, Base):
+        pass
+
+    assert Model().a is None
+
+
+def test_can_inherit_stdlib_dataclasses_default_factories_and_use_them():
+    """This test documents that default factories are not supported"""
+
+    @dataclasses.dataclass
+    class Base:
+        a: str = dataclasses.field(default_factory=lambda: 'TEST')
+
+    class Model(BaseModel, Base):
+        pass
+
+    with pytest.raises(ValidationError):
+        assert Model().a == 'TEST'
+
+
+def test_can_inherit_stdlib_dataclasses_default_factories_and_provide_a_value():
+    @dataclasses.dataclass
+    class Base:
+        a: str = dataclasses.field(default_factory=lambda: 'TEST')
+
+    class Model(BaseModel, Base):
+        pass
+
+    assert Model(a='NOT_THE_SAME').a == 'NOT_THE_SAME'
+
+
+def test_can_inherit_stdlib_dataclasses_with_dataclass_fields():
+    @dataclasses.dataclass
+    class Base:
+        a: int = dataclasses.field(default=5)
+
+    class Model(BaseModel, Base):
+        pass
+
+    assert Model().a == 5