-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add basic support for recursive TypeVar defaults (PEP 696) #16878
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1954,6 +1954,15 @@ class Foo(Bar, Generic[T]): ... | |
del base_type_exprs[i] | ||
tvar_defs: list[TypeVarLikeType] = [] | ||
for name, tvar_expr in declared_tvars: | ||
tvar_expr_default = tvar_expr.default | ||
if isinstance(tvar_expr_default, UnboundType): | ||
# Assumption here is that the names cannot be duplicated | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately that's not a safe assumption There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have an example I could try? Mypy will already complain about this here and reject the second T1 = TypeVar("T1", default=int)
T1 = TypeVar("T1", default=str) # error
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you have to import the other TypeVar from a different module. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That results in the same error message. The second E.g. the comparison is with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought something like this might fail:
But this passes as expected, and I couldn't find a variation that fails. So I suppose this is fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This variation fails:
Not code I'd expect anyone to actually write, but it does seem like it should be legal. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the test, that helped! Tbh I could also have though about this earlier but it's actually quite simple to add a name lookup here 😅 |
||
# TODO: - detect out of order and self-referencing TypeVars | ||
# - nested default types, e.g. list[T1] | ||
for fullname, type_var in self.tvar_scope.scope.items(): | ||
type_var_name = fullname.rpartition(".")[2] | ||
if tvar_expr_default.name == type_var_name: | ||
tvar_expr.default = type_var | ||
tvar_def = self.tvar_scope.bind_new(name, tvar_expr) | ||
tvar_defs.append(tvar_def) | ||
return base_type_exprs, tvar_defs, is_protocol | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we
.copy_modified()
here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't work, unfortunately. This here is used to link nested TypeVarTypes so if it's changed / evaluated in one location the other stays in sync.
An example from the tests
For the first pass the type variables are as follows
The section here now replaces the default for
T2
with the tbd of the default ofT1
:--
With
.copy_modified
it would be a copy of theT1'1
thus when it's evaluated it's not used forT2
. The test would still returnAt least that's how I understand it 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked at it more closely. It's basically as said above. In particular, it's called in
freshend_function_type_vars
. We enter this function with linked TypeVarsThen create new ones for each and link them again in
expand_type
, so thatmypy/mypy/expandtype.py
Lines 119 to 130 in 517f5ae