-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Support for functions producing generic functions #3113
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
Changes from 1 commit
679a172
74e9262
5470c77
12def8b
52782fe
0184595
757a6b8
57bca93
3b8bc23
82da335
9f12644
8cf4fa6
2634277
30dba10
7b4f9f2
daf9592
826b9cf
4141f94
58e3a2a
c061a09
603013d
c6fec63
f46d52a
ea21337
80d62a7
eaf8c0d
5009a2b
7c64464
a866d0f
9d5c630
4683d70
cbb3cb0
da0d936
edbecb0
21d8b13
214aebc
5405646
a25e926
4aaab6c
fd153ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
… into TypeVarScope This allows us to be more consistent about which type variables we allow and which we don't.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,28 +4,58 @@ | |
|
||
|
||
class TypeVarScope: | ||
"""Scope that holds bindings for type variables. Node fullname -> TypeVarDef. | ||
"""Scope that holds bindings for type variables. Node fullname -> TypeVarDef.""" | ||
|
||
Provide a parent on intitalization when you want to make a child scope.""" | ||
def __init__(self, | ||
parent: Optional['TypeVarScope'] = None, | ||
is_class_scope: bool = False, | ||
prohibited: Optional['TypeVarScope'] = None) -> None: | ||
"""Initializer for TypeVarScope | ||
|
||
def __init__(self, parent: Optional['TypeVarScope'] = None) -> None: | ||
Parameters: | ||
parent: the outer scope for this scope | ||
is_class_scope: True if this represents a generic class | ||
prohibited: Type variables that aren't strictly in scope exactly, | ||
but can't be bound because they're part of an outer class's scope. | ||
""" | ||
self.scope = {} # type: Dict[str, TypeVarDef] | ||
self.parent = parent | ||
self.func_id = 0 | ||
self.class_id = 0 | ||
self.is_class_scope = is_class_scope | ||
self.prohibited = prohibited | ||
if parent is not None: | ||
self.func_id = parent.func_id | ||
self.class_id = parent.class_id | ||
|
||
def bind_fun_tvar(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: | ||
self.func_id -= 1 | ||
return self._bind(name, tvar_expr, self.func_id) | ||
def get_function_scope(self) -> Optional['TypeVarScope']: | ||
it = self | ||
while it.is_class_scope: | ||
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. Should the check be 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. Yes, that's safer. We have an invariant that the root is an empty function scope, with the shape of the code now, but that could change. |
||
it = it.parent | ||
return it | ||
|
||
def bind_class_tvar(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: | ||
self.class_id += 1 | ||
return self._bind(name, tvar_expr, self.class_id) | ||
def allow_binding(self, fullname: str) -> bool: | ||
if fullname in self.scope: | ||
return False | ||
elif self.parent and not self.parent.allow_binding(fullname): | ||
return False | ||
elif self.prohibited and not self.prohibited.allow_binding(fullname): | ||
return False | ||
return True | ||
|
||
def method_frame(self) -> 'TypeVarScope': | ||
return TypeVarScope(self, False, None) | ||
|
||
def _bind(self, name: str, tvar_expr: TypeVarExpr, i: int) -> TypeVarDef: | ||
def class_frame(self) -> 'TypeVarScope': | ||
return TypeVarScope(self.get_function_scope(), True, self) | ||
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. Should we assert that the current scope is a class scope? If not, should we still prohibit the definitions in the current scope even if it's a function scope? 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. You are always prohibited from re-binding an already-bound type variable. When you define a class in a function, this doesn't prohibit anything more -- your class type variables are also going to be prohibited by dint of being in your own parent. |
||
|
||
def bind(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: | ||
if self.is_class_scope: | ||
self.class_id += 1 | ||
i = self.class_id | ||
else: | ||
self.func_id -= 1 | ||
i = self.func_id | ||
tvar_def = TypeVarDef( | ||
name, i, values=tvar_expr.values, | ||
upper_bound=tvar_expr.upper_bound, variance=tvar_expr.variance, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -408,7 +408,7 @@ def analyze_callable_type(self, t: UnboundType) -> Type: | |
@contextmanager | ||
def tvar_scope_frame(self) -> Generator: | ||
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. Use |
||
old_scope = self.tvar_scope | ||
self.tvar_scope = TypeVarScope(self.tvar_scope) | ||
self.tvar_scope = self.tvar_scope.method_frame() | ||
yield | ||
self.tvar_scope = old_scope | ||
|
||
|
@@ -437,16 +437,19 @@ def bind_function_type_variables(self, | |
for var in fun_type.variables: | ||
var_expr = self.lookup(var.name, var).node | ||
assert isinstance(var_expr, TypeVarExpr) | ||
self.tvar_scope.bind_fun_tvar(var.name, var_expr) | ||
self.tvar_scope.bind(var.name, var_expr) | ||
return fun_type.variables | ||
typevars = self.infer_type_variables(fun_type) | ||
# Do not define a new type variable if already defined in scope. | ||
typevars = [(name, tvar) for name, tvar in typevars | ||
if not self.is_defined_type_var(name, defn)] | ||
defs = [] # type: List[TypeVarDef] | ||
for name, tvar in typevars: | ||
self.tvar_scope.bind_fun_tvar(name, tvar) | ||
if not self.tvar_scope.allow_binding(tvar.fullname()): | ||
self.fail("Type variable '{}' is bound by an outer class".format(name), defn) | ||
self.tvar_scope.bind(name, tvar) | ||
defs.append(self.tvar_scope.get_binding(tvar.fullname())) | ||
|
||
return defs | ||
|
||
def is_defined_type_var(self, tvar: str, context: Context) -> bool: | ||
|
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.
Style nit: indent the parameter descriptions like this (for better consistency):