Describe the Bug
I’m seeing what looks like a false positive bad-return when calling pydantic.BaseModel.model_copy() inside an isinstance branch on a generic type variable.
Notably, I included a control example with a similar copy(self) -> Self method (Copyable) that does type-check successfully in the same pattern. That suggests this is specific to pyrefly’s handling/integration of pydantic BaseModel.model_copy() rather than a general issue with Self + narrowed TypeVar.
Minimal repro
from copy import copy
from typing import Self, reveal_type
from pydantic import BaseModel
# Working example
class Copyable:
def copy(self) -> Self:
return copy(self)
class CopyableChild:
pass
def test[T: Copyable](value: T) -> T:
if isinstance(value, CopyableChild):
reveal_type(value) # revealed type is `CopyableChild & T`
value_copy = value.copy()
reveal_type(value_copy) # revealed type is `CopyableChild & T`
return value_copy
else:
reveal_type(value)
return value
# Error reproduction
class ParenModel(BaseModel):
field: int | str
class ChildModel(BaseModel):
field: int
def test2[T: ParenModel](value: T) -> T:
if isinstance(value, ChildModel):
reveal_type(value) # revealed type is `ChildModel & T`
value_copy = value.model_copy()
reveal_type(value_copy) # revealed type is `ChildModel` instead of `ChildModel & T`
return value_copy
else:
reveal_type(value)
return value
pyrefly output:
> pyrefly check test.py
INFO revealed type: CopyableChild & T [reveal-type]
--> test.py:19:20
|
19 | reveal_type(value) # revealed type is `CopyableChild & T`
| -------
|
INFO revealed type: T [reveal-type]
--> test.py:21:20
|
21 | reveal_type(value_copy) # revealed type is `CopyableChild & T`
| ------------
|
INFO revealed type: T [reveal-type]
--> test.py:24:20
|
24 | reveal_type(value)
| -------
|
INFO revealed type: ChildModel & T [reveal-type]
--> test.py:39:20
|
39 | reveal_type(value) # revealed type is `ChildModel & T`
| -------
|
INFO revealed type: ChildModel [reveal-type]
--> test.py:41:20
|
41 | reveal_type(value_copy) # revealed type is `ChildModel` instead of `ChildModel & T`
| ------------
|
ERROR Returned type `ChildModel` is not assignable to declared return type `T` [bad-return]
--> test.py:42:16
|
37 | def test2[T: ParenModel](value: T) -> T:
| - declared return type
38 | if isinstance(value, ChildModel):
39 | reveal_type(value) # revealed type is `ChildModel & T`
40 | value_copy = value.model_copy()
41 | reveal_type(value_copy) # revealed type is `ChildModel` instead of `ChildModel & T`
42 | return value_copy
| ^^^^^^^^^^
|
INFO revealed type: T [reveal-type]
--> test.py:44:20
|
44 | reveal_type(value)
| -------
|
INFO 1 error
Environment
- pyrefly 0.60.2
- Python 3.14.3
- pydantic 2.12.5
Sandbox Link
No response
(Only applicable for extension issues) IDE Information
No response
Describe the Bug
I’m seeing what looks like a false positive
bad-returnwhen callingpydantic.BaseModel.model_copy()inside anisinstancebranch on a generic type variable.Notably, I included a control example with a similar
copy(self) -> Selfmethod (Copyable) that does type-check successfully in the same pattern. That suggests this is specific to pyrefly’s handling/integration of pydanticBaseModel.model_copy()rather than a general issue withSelf+ narrowedTypeVar.Minimal repro
pyrefly output:
Environment
Sandbox Link
No response
(Only applicable for extension issues) IDE Information
No response