Repository: pydantic/pydantic

Issue:
Mypy plugin crashes with `explicit_package_bases` set as true
### Initial Checks

- [X] I have searched GitHub for a duplicate issue and I'm sure this is something new
- [X] I have searched Google & StackOverflow for a solution and couldn't find anything
- [X] I have read and followed [the docs](https://docs.pydantic.dev) and still think this is a bug
- [X] I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like [FastAPI](https://fastapi.tiangolo.com) or [mypy](https://mypy.readthedocs.io/en/stable))


### Description

After updating mypy to v1.1.1, I added the following `pyproject.toml` config, as descripted in the [documentation](https://docs.pydantic.dev/mypy_plugin/#plugin-settings), to prevent mypy errors on classes with Optional attributes:

``` txt
[tool.mypy]
strict = true
files = "src/app"
mypy_path = "src/"
namespace_packages = true
explicit_package_bases = true
follow_imports = "silent"
ignore_missing_imports = true
exclude = "(resources/.*)"

plugins = [
    "pydantic.mypy"
]

warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = true

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true
```

Using that config, I found an internal error while running mypy
```
src/app/models/stages.py:152: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 1.1.1
Traceback (most recent call last):
  File "mypy/semanal.py", line 6261, in accept
  File "mypy/nodes.py", line 1123, in accept
  File "mypy/semanal.py", line 1561, in visit_class_def
  File "mypy/semanal.py", line 1641, in analyze_class
  File "mypy/semanal.py", line 1668, in analyze_class_body_common
  File "mypy/semanal.py", line 1751, in apply_class_plugin_hooks
  File "pydantic/mypy.py", line 150, in pydantic.mypy.PydanticPlugin._pydantic_model_class_maker_callback
  File "pydantic/mypy.py", line 315, in pydantic.mypy.PydanticModelTransformer.transform
  File "pydantic/mypy.py", line 524, in pydantic.mypy.PydanticModelTransformer.set_frozen
AssertionError: 
src/app/models/stages.py:152: : note: use --pdb to drop into pdb
```

If remove the `explicit_package_bases` configuration, mypy is able to run without this setting.

mypy version:
```
 name         : mypy                              
 version      : 1.1.1                             
 description  : Optional static typing for Python 

dependencies
 - mypy-extensions >=1.0.0
 - tomli >=1.1.0
 - typing-extensions >=3.10
 ```



### Example Code

_No response_

### Python, Pydantic & OS Version

```Text
pydantic version: 1.10.6
pydantic compiled: True
install path: /workspace/.venv/lib/python3.9/site-packages/pydantic
python version: 3.9.16 (main, Jan 11 2023, 15:55:30)  [GCC 10.2.1 20210110]
platform: Linux-5.10.102.1-microsoft-standard-WSL2-x86_64-with-glibc2.31
optional deps. installed: ['typing-extensions']
```


### Affected Components

- [ ] [Compatibility between releases](https://docs.pydantic.dev/changelog/)
- [ ] [Data validation/parsing](https://docs.pydantic.dev/usage/models/#basic-model-usage)
- [ ] [Data serialization](https://docs.pydantic.dev/usage/exporting_models/) - `.dict()` and `.json()`
- [ ] [JSON Schema](https://docs.pydantic.dev/usage/schema/)
- [ ] [Dataclasses](https://docs.pydantic.dev/usage/dataclasses/)
- [ ] [Model Config](https://docs.pydantic.dev/usage/model_config/)
- [ ] [Field Types](https://docs.pydantic.dev/usage/types/) - adding or changing a particular data type
- [ ] [Function validation decorator](https://docs.pydantic.dev/usage/validation_decorator/)
- [ ] [Generic Models](https://docs.pydantic.dev/usage/models/#generic-models)
- [ ] [Other Model behaviour](https://docs.pydantic.dev/usage/models/) - `construct()`, pickling, private attributes, ORM mode
- [X] [Plugins](https://docs.pydantic.dev/) and integration with other tools - mypy, FastAPI, python-devtools, Hypothesis, VS Code, PyCharm, etc.


Discussion:
See #5192
@marcelopgc I'm looking at this now — do you have example code you can share that causes this issue when you run mypy on it? At the very least I will make it produce an error that we can debug as opposed to crashing mypy, but if I can reproduce the issue I should be able to resolve it.

Resolving patch:
diff --git a/pydantic/mypy.py b/pydantic/mypy.py
--- a/pydantic/mypy.py
+++ b/pydantic/mypy.py
@@ -522,13 +522,26 @@ def set_frozen(self, fields: List['PydanticModelField'], frozen: bool) -> None:
 
         This is the same approach used by the attrs and dataclasses plugins.
         """
-        info = self._ctx.cls.info
+        ctx = self._ctx
+        info = ctx.cls.info
         for field in fields:
             sym_node = info.names.get(field.name)
             if sym_node is not None:
                 var = sym_node.node
-                assert isinstance(var, Var)
-                var.is_property = frozen
+                if isinstance(var, Var):
+                    var.is_property = frozen
+                elif isinstance(var, PlaceholderNode) and not ctx.api.final_iteration:
+                    # See https://github.com/pydantic/pydantic/issues/5191 to hit this branch for test coverage
+                    ctx.api.defer()
+                else:  # pragma: no cover
+                    # I don't know whether it's possible to hit this branch, but I've added it for safety
+                    try:
+                        var_str = str(var)
+                    except TypeError:
+                        # This happens for PlaceholderNode; perhaps it will happen for other types in the future..
+                        var_str = repr(var)
+                    detail = f'sym_node.node: {var_str} (of type {var.__class__})'
+                    error_unexpected_behavior(detail, ctx.api, ctx.cls)
             else:
                 var = field.to_var(info, use_alias=False)
                 var.info = info
@@ -754,7 +767,9 @@ def error_required_dynamic_aliases(api: SemanticAnalyzerPluginInterface, context
     api.fail('Required dynamic aliases disallowed', context, code=ERROR_ALIAS)
 
 
-def error_unexpected_behavior(detail: str, api: CheckerPluginInterface, context: Context) -> None:  # pragma: no cover
+def error_unexpected_behavior(
+    detail: str, api: Union[CheckerPluginInterface, SemanticAnalyzerPluginInterface], context: Context
+) -> None:  # pragma: no cover
     # Can't think of a good way to test this, but I confirmed it renders as desired by adding to a non-error path
     link = 'https://github.com/pydantic/pydantic/issues/new/choose'
     full_message = f'The pydantic mypy plugin ran into unexpected behavior: {detail}\n'


Test patch: