Repository: pydantic/pydantic

Issue:
Failure to override an inherited field with a computed field
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

It seems that overriding a regular field with a computed field doesn't behave as expected. Instead of a value being returned by accessing the property the property object itself is returned. And as for `@cached_property` causes an exception within `pydantic/_internal/_utils.py:smart_deepcopy`.

### Example Code

```Python
from pydantic import BaseModel, computed_field
from functools import cached_property

class Foo(BaseModel):
    name: str = "Foo"

class Bar(Foo):
    @computed_field
    @property
    def name(self) -> str:
        return "Bar"

print(Foo().name)  # Foo
print(Bar().name)  # <property object at 0x...>

# Upon definition:
# > TypeError: cannot pickle '_thread.RLock' object
class Baz(Foo):
    @computed_field
    @cached_property
    def name(self) -> str:
        return "Baz"
```

Edit: After some further playing around, removing the default `name = "Foo"` from `Foo` it's clear that `name` within `Bar` is in some kind of weird hybrid state.

```Python
class Foo(BaseModel):
    name: str

class Bar(Foo):
    @computed_field
    @property
    def name(self) -> str:
        print("from property")
        return "Bar"

# Pyright complains:
# 1. Pyright: Argument missing for parameter "name" [reportGeneralTypeIssues]
# But runs and I get the same result
print(Bar().name)  # <property object at 0x...>

# Prints name as inherited from Foo, and without the `"from property"`
print(Bar(name="Bar"))  # Bar
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.1.1
        pydantic-core version: 2.4.0
          pydantic-core build: profile=release pgo=false mimalloc=true
                 install path: /Users/.../.pyenv/versions/3.11.1/lib/python3.11/site-packages/pydantic
               python version: 3.11.1 (main, Jan 14 2023, 16:44:31) [Clang 13.1.6 (clang-1316.0.21.2.5)]
                     platform: macOS-13.4-arm64-arm-64bit
     optional deps. installed: ['email-validator', 'typing-extensions']
```


Selected Assignee: @hramezani


Discussion:
Looks like a bug. We should either fix it so in this example `name` is removed from fields, or raise an error.

BTW, there's no way to avoid the pyright complaint since that's just dataclass transforms doing their thing.

PR welcome to fix this.
Should this even be allowed? In general, overriding an attribute with a property is not allowed, e.g. because it cannot be assigned to. So even if we could allow this, I think it's technically incorrect to allow it. It wouldn't be the first time we do something "not theoretically correct" for the sake of usability, but we should maybe rethink if this is complex to implement as well. @sydney-runkle is going to look into what the implementation would look like.
Here's an example:

```python
class Foo:
    name: str

class Bar(Foo):
    @property
    def name(self) -> str:
        raise NotImplementedError
```

MyPy says:

```
error: Cannot override writeable attribute with read-only property  [override]
Found 1 error in 1 file (checked 1 source file)
```

Pyright says:

```
error: "name" overrides symbol of same name in class "Foo"
    "property" is incompatible with "str" (reportIncompatibleVariableOverride)
```

Even within a Pydantic context:

```python
from pydantic import BaseModel, computed_field


class Foo(BaseModel):
    name: str

class Bar(Foo):
    @computed_field
    @property
    def name(self) -> str:
        raise NotImplementedError


def f(a: Foo) -> Foo:
    a.name *= 2
    return a

f(Foo(name='a'))
f(Bar()) # TypeError: unsupported operand type(s) for *=: 'property' and 'int'
```

Even without the runtime error type checkers that use [PEP 681](https://peps.python.org/pep-0681/) won't handle this override correctly (which was pointed out in the issue's first comment).

Resolving patch:
diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py
--- a/pydantic/_internal/_fields.py
+++ b/pydantic/_internal/_fields.py
@@ -19,6 +19,7 @@
     from ..fields import FieldInfo
     from ..main import BaseModel
     from ._dataclasses import StandardDataclass
+    from ._decorators import DecoratorInfos
 
 
 def get_type_hints_infer_globalns(
@@ -196,6 +197,11 @@ def collect_model_fields(  # noqa: C901
             except AttributeError:
                 pass  # indicates the attribute was on a parent class
 
+        # Use cls.__dict__['__pydantic_decorators__'] instead of cls.__pydantic_decorators__
+        # to make sure the decorators have already been built for this exact class
+        decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__']
+        if ann_name in decorators.computed_fields:
+            raise ValueError("you can't override a field with a computed field")
         fields[ann_name] = field_info
 
     if typevars_map:


Test patch:
diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py
--- a/tests/test_computed_fields.py
+++ b/tests/test_computed_fields.py
@@ -741,3 +741,16 @@ def double_x(self) -> T:
 
     with pytest.warns(UserWarning, match='Expected `int` but got `str` - serialized value may not be as expected'):
         B[int]().model_dump()
+
+
+def test_computed_field_override_raises():
+    class Model(BaseModel):
+        name: str = 'foo'
+
+    with pytest.raises(ValueError, match="you can't override a field with a computed field"):
+
+        class SubModel(Model):
+            @computed_field
+            @property
+            def name(self) -> str:
+                return 'bar'