def make_tvar_init_expression(self, info: TypeInfo, slot: int) -> TypeExpr: """Return the initializer for the given slot in the given type. This is the type expression that initializes the given slot using the type arguments given to the constructor. Examples: - In 'class C(Generic[T]) ...', the initializer for the slot 0 is TypeExpr(RuntimeTypeVar(NameExpr('__tv'))). - In 'class D(C[int]) ...', the initializer for the slot 0 is TypeExpr(<int instance>). """ # Figure out the superclass which defines the slot; also figure out # the tvar index that maps to the slot. origin, tv = find_slot_origin(info, slot) # Map self type to the superclass -> extract tvar with target index # (only contains subclass tvars?? PROBABLY NOT). selftype = self_type(info) selftype = map_instance_to_supertype(selftype, origin) tvar = selftype.args[tv - 1] # Map tvar to an expression; refer to local vars instead of member # vars always. tvar = translate_runtime_type_vars_locally(tvar) # Build the rvalue (initializer) expression return TypeExpr(tvar)
def make_superclass_constructor_call( self, info: TypeInfo, callee_type: Callable) -> ExpressionStmt: """Construct a statement that calls the superclass constructor. In particular, it passes any type variables arguments as needed. """ callee = SuperExpr('__init__') callee.info = info # We do not handle generic constructors. Either pass runtime # type variables from the current scope or perhaps require # explicit constructor in this case. selftype = self_type(info) # FIX overloading # FIX default args / varargs # Map self type to the superclass context. base = info.mro[1] selftype = map_instance_to_supertype(selftype, base) super_init = cast(FuncDef, base.get_method('__init__')) # Add constructor arguments. args = [] # type: List[Node] for n in range(1, callee_type.min_args): args.append(NameExpr(super_init.args[n].name())) self.tf.set_type(args[-1], callee_type.arg_types[n]) # Store callee type after stripping away the 'self' type. self.tf.set_type(callee, nodes.method_callable(callee_type)) call = CallExpr(callee, args, [nodes.ARG_POS] * len(args)) return ExpressionStmt(call)
def compile_slot_mapping(typ: TypeInfo) -> List[Type]: """Return types that represent values of type variable slots of a type. The returned types are in terms of type variables of the type. For example, assume these definitions: class D(Generic[S]): ... class C(D[E[S]], Generic[T, S]): ... Now slot mappings for C is [E[S], T] (S and T refer to type variables of C). """ exprs = [] # type: List[Type] for slot in range(num_slots(typ)): # Figure out the superclass which defines the slot; also figure out # the tvar index that maps to the slot. origin, tv = find_slot_origin(typ, slot) # Map self type to the superclass -> extract tvar with target index # (only contains subclass tvars?? PROBABLY NOT). selftype = self_type(typ) selftype = map_instance_to_supertype(selftype, origin) tvar = selftype.args[tv - 1] # tvar is the representation of the slot in terms of type arguments. exprs.append(tvar) return exprs
def make_tvar_init_expression(self, info, slot): """Return the initializer for the given slot in the given type. This is the type expression that initializes the given slot using the type arguments given to the constructor. Examples: - In 'class C<T> ...', the initializer for the slot 0 is TypeExpr(RuntimeTypeVar(NameExpr('__tv'))). - In 'class D(C<int>) ...', the initializer for the slot 0 is TypeExpr(<int instance>). """ # Figure out the superclass which defines the slot; also figure out # the tvar index that maps to the slot. origin, tv = find_slot_origin(info, slot) # Map self type to the superclass -> extract tvar with target index # (only contains subclass tvars?? PROBABLY NOT). selftype = self_type(info) selftype = map_instance_to_supertype(selftype, origin) tvar = selftype.args[tv - 1] # Map tvar to an expression; refer to local vars instead of member # vars always. tvar = translate_runtime_type_vars_locally(tvar) # Build the rvalue (initializer) expression return TypeExpr(tvar)
def make_superclass_constructor_call(self, info, callee_type): """Construct a statement that calls the superclass constructor. In particular, it passes any type variables arguments as needed. """ callee = SuperExpr('__init__') callee.info = info # We do not handle generic constructors. Either pass runtime # type variables from the current scope or perhaps require # explicit constructor in this case. selftype = self_type(info) # FIX overloading # FIX default args / varargs # Map self type to the superclass context. selftype = map_instance_to_supertype(selftype, info.base) super_init = info.base.get_method('__init__') # Add constructor arguments. args = [] for n in range(1, callee_type.min_args): args.append(NameExpr(super_init.args[n].name())) self.tf.set_type(args[-1], callee_type.arg_types[n]) # Store callee type after stripping away the 'self' type. self.tf.set_type(callee, nodes.method_callable(callee_type)) call = CallExpr(callee, args, [nodes.ARG_POS] * len(args)) return ExpressionStmt(call)
def map_type_from_supertype(typ: Type, sub_info: TypeInfo, super_info: TypeInfo) -> Type: """Map type variables in a type defined in a supertype context to be valid in the subtype context. Assume that the result is unique; if more than one type is possible, return one of the alternatives. For example, assume . class D(Generic[S]) ... . class C(D[E[T]], Generic[T]) ... Now S in the context of D would be mapped to E[T] in the context of C. """ # Create the type of self in subtype, of form t[a1, ...]. inst_type = self_type(sub_info) if isinstance(inst_type, TupleType): inst_type = inst_type.fallback # Map the type of self to supertype. This gets us a description of the # supertype type variables in terms of subtype variables, i.e. t[t1, ...] # so that any type variables in tN are to be interpreted in subtype # context. inst_type = map_instance_to_supertype(inst_type, super_info) # Finally expand the type variables in type with those in the previously # constructed type. Note that both type and inst_type may have type # variables, but in type they are interpreted in supertype context while # in inst_type they are interpreted in subtype context. This works even if # the names of type variables in supertype and subtype overlap. return expand_type_by_instance(typ, inst_type)
def map_type_from_supertype(typ, sub_info, super_info): """Map type variables in a type defined in a supertype context to be valid in the subtype context. Assume that the result is unique; if more than one type is possible, return one of the alternatives. For example, assume class D<S> ... class C<T> is D<E<T>> ... Now S in the context of D would be mapped to E<T> in the context of C. """ # Create the type of self in subtype, of form t<a1, ...>. inst_type = self_type(sub_info) # Map the type of self to supertype. This gets us a description of the # supertype type variables in terms of subtype variables, i.e. t<t1, ...> # so that any type variables in tN are to be interpreted in subtype # context. inst_type = map_instance_to_supertype(inst_type, super_info) # Finally expand the type variables in type with those in the previously # constructed type. Note that both type and inst_type may have type # variables, but in type they are interpreterd in supertype context while # in inst_type they are interpreted in subtype context. This works even if # the names of type variables in supertype and subtype overlap. return expand_type_by_instance(typ, inst_type)
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, special_sig: Optional[str]) -> CallableType: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] variables.extend(info.defn.type_vars) variables.extend(init_type.variables) callable_type = init_type.copy_modified( ret_type=self_type(info), fallback=type_type, name=None, variables=variables, special_sig=special_sig) c = callable_type.with_name('"{}"'.format(info.name())) c.is_classmethod_class = True return c
def class_callable(init_type: Callable, info: TypeInfo) -> Callable: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i, tvar in enumerate(info.defn.type_vars): variables.append(TypeVarDef(tvar.name, i + 1, tvar.values)) initvars = init_type.variables variables.extend(initvars) c = Callable(init_type.arg_types, init_type.arg_kinds, init_type.arg_names, self_type(info), True, None, variables).with_name('"{}"'.format(info.name())) return convert_class_tvars_to_func_tvars(c, len(initvars))
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance) -> CallableType: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i, tvar in enumerate(info.defn.type_vars): variables.append(TypeVarDef(tvar.name, i + 1, tvar.values, tvar.upper_bound, tvar.variance)) initvars = init_type.variables variables.extend(initvars) c = CallableType( init_type.arg_types, init_type.arg_kinds, init_type.arg_names, self_type(info), type_type, None, variables ).with_name('"{}"'.format(info.name())) return convert_class_tvars_to_func_tvars(c, len(initvars))
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance) -> CallableType: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i, tvar in enumerate(info.defn.type_vars): variables.append(TypeVarDef(tvar.name, i + 1, tvar.values, tvar.upper_bound, tvar.variance)) initvars = init_type.variables variables.extend(initvars) callable_type = init_type.copy_modified( ret_type=self_type(info), fallback=type_type, name=None, variables=variables) c = callable_type.with_name('"{}"'.format(info.name())) return convert_class_tvars_to_func_tvars(c, len(initvars))
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance) -> CallableType: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i, tvar in enumerate(info.defn.type_vars): variables.append(TypeVarDef(tvar.name, i + 1, tvar.values, tvar.upper_bound, tvar.variance)) initvars = init_type.variables variables.extend(initvars) callable_type = init_type.copy_modified( ret_type=self_type(info), fallback=type_type, name=None, variables=variables) c = callable_type.with_name('"{}"'.format(info.name())) cc = convert_class_tvars_to_func_tvars(c, len(initvars)) cc.is_classmethod_class = True return cc
def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, special_sig: Optional[str]) -> CallableType: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] variables.extend(info.defn.type_vars) variables.extend(init_type.variables) callable_type = init_type.copy_modified(ret_type=self_type(info), fallback=type_type, name=None, variables=variables, special_sig=special_sig) c = callable_type.with_name('"{}"'.format(info.name())) c.is_classmethod_class = True return c
def class_callable(init_type: Callable, info: TypeInfo) -> Callable: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i in range(len(info.type_vars)): # TODO bounds variables.append(TypeVarDef(info.type_vars[i], i + 1, None)) initvars = init_type.variables variables.extend(initvars) c = Callable(init_type.arg_types, init_type.arg_kinds, init_type.arg_names, self_type(info), True, None, variables).with_name('"{}"'.format(info.name())) return convert_class_tvars_to_func_tvars(c, len(initvars))
def make_dynamic_setter_wrapper(self, name, typ): """Create a dynamically-typed setter wrapper for a data attribute. The setter will be of this form: . void set$name*(C self, any name): . self.name! = {typ name} """ lvalue = MemberExpr(self_expr(), name, direct=True) name_expr = NameExpr(name) rvalue = coerce(name_expr, typ, Any(), self.tf.type_context()) ret = AssignmentStmt([lvalue], rvalue) wrapper_name = 'set$' + name + self.tf.dynamic_suffix() selft = self_type(self.tf.type_context()) sig = Callable([selft, Any()], [nodes.ARG_POS, nodes.ARG_POS], [None, None], Void(), False) return FuncDef(wrapper_name, [Var('self'), Var(name)], [nodes.ARG_POS, nodes.ARG_POS], [None, None], Block([ret]), sig)
def make_dynamic_setter_wrapper(self, name: str, typ: Type) -> FuncDef: """Create a dynamically-typed setter wrapper for a data attribute. The setter will be of this form: . def set$name*(self: C, name; Any) -> None: . self.name! = {typ name} """ lvalue = MemberExpr(self_expr(), name, direct=True) name_expr = NameExpr(name) rvalue = coerce(name_expr, typ, AnyType(), self.tf.type_context()) ret = AssignmentStmt([lvalue], rvalue) wrapper_name = 'set$' + name + self.tf.dynamic_suffix() selft = self_type(self.tf.type_context()) sig = Callable([selft, AnyType()], [nodes.ARG_POS, nodes.ARG_POS], [None, None], Void(), False) return FuncDef(wrapper_name, [Var('self'), Var(name)], [nodes.ARG_POS, nodes.ARG_POS], [None, None], Block([ret]), sig)
. class C<T, S>(D<E<S>>): ... . class D<S>(object): ... Now slot mappings for C is [E<S>, T] (S and T refer to type variables of C). """ Type[] exprs = [] for slot in range(num_slots(typ)): # Figure out the superclass which defines the slot; also figure out # the tvar index that maps to the slot. origin, tv = find_slot_origin(typ, slot) # Map self type to the superclass -> extract tvar with target index # (only contains subclass tvars?? PROBABLY NOT). selftype = self_type(typ) selftype = map_instance_to_supertype(selftype, origin) tvar = selftype.args[tv - 1] # tvar is the representation of the slot in terms of type arguments. exprs.append(tvar) return exprs tuple<TypeInfo, int> find_slot_origin(TypeInfo info, int slot): """Determine class and type variable index that directly maps to the slot. The result defines which class in inheritance hierarchy of info introduced the slot. All subclasses inherit this slot. The result TypeInfo always refers to one of the base classes of info (or info itself).
def self_type(self): return self_type(self.tf.type_context())
def make_init_wrapper(self, tdef: ClassDef) -> List[Node]: """Make and return an implicit __init__ if class needs it. Otherwise, return an empty list. We include an implicit __init__ if the class is generic or if it extends a generic class and if it does not define __init__. The __init__ of a generic class requires one or more extra type variable arguments. The inherited __init__ may not accept these. For example, assume these definitions: . class A(Generic[T]): pass . class B(A[int]): pass The constructor for B will be (equivalent to) . def __init__(self: B) -> None: . self.__tv = <int> . super().__init__(<int>) """ # FIX overloading, default args / varargs, keyword args info = tdef.info if '__init__' not in info.names and ( tdef.is_generic() or (info.bases and info.mro[1].is_generic())): # Generic class with no explicit __init__ method # (i.e. __init__ inherited from superclass). Generate a # wrapper that initializes type variable slots and calls # the superclass __init__ method. base = info.mro[1] selftype = self_type(info) callee_type = cast(Callable, analyse_member_access( '__init__', selftype, None, False, True, None, None, base)) # Now the callee type may contain the type variables of a # grandparent as bound type variables, but we want the # type variables of the parent class. Explicitly set the # bound type variables. callee_type = self.fix_bound_init_tvars(callee_type, map_instance_to_supertype(selftype, base)) super_init = cast(FuncDef, base.get_method('__init__')) # Build argument list. args = [Var('self')] for i in range(1, len(super_init.args)): args.append(Var(super_init.args[i].name())) args[-1].type = callee_type.arg_types[i - 1] selft = self_type(self.tf.type_context()) callee_type = prepend_arg_type(callee_type, selft) creat = FuncDef('__init__', args, super_init.arg_kinds, [None] * len(args), Block([])) creat.info = tdef.info creat.type = callee_type creat.is_implicit = False tdef.info.names['__init__'] = SymbolTableNode(MDEF, creat, typ=creat.type) # Insert a call to superclass constructor. If the # superclass is object, the constructor does nothing => # omit the call. if base.fullname() != 'builtins.object': creat.body.body.append( self.make_superclass_constructor_call(tdef.info, callee_type)) # Implicit cast from FuncDef[] to Node[] is safe below. return Any(self.func_tf.transform_method(creat)) else: return []
def make_init_wrapper(self, tdef): """Make and return an implicit __init__ if class needs it. Otherwise, return an empty list. We include an implicit __init__ if the class is generic or if it extends a generic class and if it does not define __init__. The __init__ of a generic class requires one or more extra type variable arguments. The inherited __init__ may not accept these. For example, assume these definitions: . class A<T>: pass . class B(A<int>): pass The constructor for B will be (equivalent to) . void __init__(B self): . self.__tv = <int> . super().__init__(<int>) """ # FIX overloading, default args / varargs, keyword args info = tdef.info if '__init__' not in info.methods and ( tdef.is_generic() or (info.base and info.base.is_generic())): # Generic class with no explicit __init__ method # (i.e. __init__ inherited from superclass). Generate a # wrapper that initializes type variable slots and calls # the superclass __init__ method. selftype = self_type(info) callee_type = analyse_member_access( '__init__', selftype, None, False, True, None, None, info.base) # Now the callee type may contain the type variables of a # grandparent as bound type variables, but we want the # type variables of the parent class. Explicitly set the # bound type variables. callee_type = self.fix_bound_init_tvars(callee_type, map_instance_to_supertype(selftype, info.base)) super_init = info.base.get_method('__init__') # Build argument list. args = [Var('self')] for i in range(1, len(super_init.args)): args.append(Var(super_init.args[i].name())) args[-1].type = callee_type.arg_types[i - 1] selft = self_type(self.tf.type_context()) callee_type = prepend_arg_type(callee_type, selft) creat = FuncDef('__init__', args, super_init.arg_kinds, [None] * len(args), Block([])) creat.info = tdef.info creat.type = callee_type creat.is_implicit = False tdef.info.methods['__init__'] = creat # Insert a call to superclass constructor. If the # superclass is object, the constructor does nothing => # omit the call. if tdef.info.base.full_name() != 'builtins.object': creat.body.body.append( self.make_superclass_constructor_call(tdef.info, callee_type)) # Implicit cast from FuncDef[] to Node[] is safe below. return self.func_tf.transform_method(creat) else: return []
. self.__tv = <int> . super().__init__(<int>) """ # FIX overloading, default args / varargs, keyword args info = tdef.info if '__init__' not in info.methods and ( tdef.is_generic() or (info.base and info.base.is_generic())): # Generic class with no explicit __init__ method # (i.e. __init__ inherited from superclass). Generate a # wrapper that initializes type variable slots and calls # the superclass __init__ method. selftype = self_type(info) callee_type = (Callable)analyse_member_access( '__init__', selftype, None, False, True, None, None, info.base) # Now the callee type may contain the type variables of a # grandparent as bound type variables, but we want the # type variables of the parent class. Explicitly set the # bound type variables. callee_type = self.fix_bound_init_tvars(callee_type, map_instance_to_supertype(selftype, info.base)) super_init = (FuncDef)info.base.get_method('__init__') # Build argument list. args = [Var('self')]
def self_type(self) -> Instance: return self_type(self.tf.type_context())