Repository: pydantic/pydantic (Python)

Issue:
PlainSerializer does not work correctly in 2.11
### Initial Checks

- [x] I confirm that I'm using Pydantic V2

### Description

In 2.10.6, the serializer worked correctly, but this changed after upgrading to version 2.11.

### Example Code 2.10

```Python
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "pydantic==2.10.6",
# ]
# ///
import pydantic
from typing import Annotated


class Foo(pydantic.BaseModel):
    spam: str = 'text'
    eggs: int = 123


type FooToJson = Annotated[
    Foo,
    pydantic.PlainSerializer(lambda v: v.model_dump_json()),
]


class Bar(pydantic.BaseModel):
    data: FooToJson = Foo()


bar = Bar().model_dump()
data_type = type(Bar().model_dump()['data'])

print(bar)
print(f'data type is {data_type}\n')

```


output:

```
{'data': '{"spam":"text","eggs":123}'}
data type is <class 'str'>
```

### Example Code 2.11
            
```python
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "pydantic==2.11.0",
# ]
# ///
import pydantic
from typing import Annotated


class Foo(pydantic.BaseModel):
    spam: str = 'text'
    eggs: int = 123


type FooToJson = Annotated[
    Foo,
    pydantic.PlainSerializer(lambda v: v.model_dump_json()),
]


class Bar(pydantic.BaseModel):
    data: FooToJson = Foo()


bar = Bar().model_dump()
data_type = type(Bar().model_dump()['data'])

print(bar)
print(f'data type is {data_type}\n')

```


output:

```
{'data': {'spam': 'text', 'eggs': 123}}
data type is <class 'dict'>
```

### Python, Pydantic & OS Version

```Text
python 3.12
Windows 10
```

```Text
             pydantic version: 2.10.6
        pydantic-core version: 2.27.2
          pydantic-core build: profile=release pgo=false
                 install path: C:\Users\LapinVMi\AppData\Local\uv\cache\environments-v2\ttt-c2977dc9225dd395\Lib\site-packages\pydantic
               python version: 3.12.4 (tags/v3.12.4:8e8a4ba, Jun  6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]
                     platform: Windows-10-10.0.19045-SP0
             related packages: typing_extensions-4.13.0
                       commit: unknown
````

```Text
             pydantic version: 2.11.0
        pydantic-core version: 2.33.0
          pydantic-core build: profile=release pgo=false
                 install path: C:\Users\LapinVMi\AppData\Local\uv\cache\environments-v2\ttt2-c05b1fa1e15714e3\Lib\site-packages\pydantic
               python version: 3.12.4 (tags/v3.12.4:8e8a4ba, Jun  6 2024, 19:30:16) [MSC v.1940 64 bit (AMD64)]
                     platform: Windows-10-10.0.19045-SP0
             related packages: typing_extensions-4.13.0
                       commit: unknown
```


Discussion:
Thanks for the report, this will be fixed in the next patch release.



Patch:
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
index 42aefcba478..c7efbf5b209 100644
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -2813,7 +2813,7 @@ def _resolve_definition(self, ref: str, definitions: dict[str, CoreSchema]) -> C
         # Some `'definition-ref'` schemas might act as "intermediate" references (e.g. when using
         # a PEP 695 type alias (which is referenceable) that references another PEP 695 type alias):
         visited: set[str] = set()
-        while definition['type'] == 'definition-ref':
+        while definition['type'] == 'definition-ref' and _inlining_behavior(definition) == 'inline':
             schema_ref = definition['schema_ref']
             if schema_ref in visited:
                 raise PydanticUserError(


Test patch:
diff --git a/tests/test_type_alias_type.py b/tests/test_type_alias_type.py
index 15f727b415e..dac87904ec7 100644
--- a/tests/test_type_alias_type.py
+++ b/tests/test_type_alias_type.py
@@ -7,7 +7,7 @@
 from annotated_types import MaxLen
 from typing_extensions import TypeAliasType
 
-from pydantic import BaseModel, PydanticUserError, TypeAdapter, ValidationError
+from pydantic import BaseModel, PlainSerializer, PydanticUserError, TypeAdapter, ValidationError, WithJsonSchema
 
 T = TypeVar('T')
 
@@ -430,3 +430,47 @@ class MyModel(BaseModel):
 
     assert exc_info.value.code == 'circular-reference-schema'
     assert exc_info.value.message.startswith('tests.test_type_alias_type.C')
+
+
+def test_type_alias_type_with_serialization() -> None:
+    """Regression test for https://github.com/pydantic/pydantic/issues/11642.
+
+    The issue lied in the definition resolving logic, which wasn't taking
+    possible metadata or serialization attached to a `'definition-ref'` schema.
+
+    In this example, the core schema for `B` — before schema cleaning — will look like:
+
+    ```python
+    {
+        'type': 'definition-ref',
+        'schema_ref': '__main__.A',
+        'serialization': {...},
+        'ref': '__main__.B',
+    }
+    ```
+
+    and the "main"/"top-level" core schema is a `'definition-ref'` schema pointing to
+    this `__main__.B` core schema.
+
+    In schema cleaning, when resolving this top-level core schema, we are recursively
+    unpack `'definition-ref'` schemas (and will end up with the actual schema for `__main__.A`),
+    without taking into account the fact that the schema of `B` had a `serialization` key!
+    """
+
+    A = TypeAliasType('A', int)
+    B = TypeAliasType('B', Annotated[A, PlainSerializer(lambda v: 3)])
+
+    ta = TypeAdapter(B)
+
+    assert ta.dump_python(1) == 3
+
+
+@pytest.mark.skip_json_schema_validation(reason='Extra info added.')
+def test_type_alias_type_with_metadata() -> None:
+    """Same as `test_type_alias_type_with_serialization()` but with JSON Metadata."""
+
+    A = TypeAliasType('A', int)
+    B = TypeAliasType('B', Annotated[A, WithJsonSchema({'type': 'int', 'extra': 1})])
+
+    ta = TypeAdapter(B)
+    assert ta.json_schema() == {'type': 'int', 'extra': 1}