def apply_type_comment(self, state, op, name, value): """If there is a type comment for the op, return its value.""" assert op is self.vm.frame.current_opcode if op.code.co_filename != self.vm.filename: return value if not op.type_comment: return value comment = op.type_comment try: frame = self.vm.frame var = abstract_utils.eval_expr(self.vm, state.node, frame.f_globals, frame.f_locals, comment) except abstract_utils.EvaluationError as e: self.vm.errorlog.invalid_type_comment(self.vm.frames, comment, details=utils.message(e)) value = self.vm.new_unsolvable(state.node) else: try: typ = abstract_utils.get_atomic_value(var) except abstract_utils.ConversionError: self.vm.errorlog.invalid_type_comment( self.vm.frames, comment, details="Must be constant.") value = self.vm.new_unsolvable(state.node) else: if self.get_type_parameters(typ): self.vm.errorlog.not_supported_yet( self.vm.frames, "using type parameter in type comment") try: value = self.init_annotation(typ, name, self.vm.frames, state.node) except self.LateAnnotationError: value = LateAnnotation(typ, name, self.vm.simple_stack()) return value
def apply_annotation(self, state, op, name, value): """If there is an annotation for the op, return its value.""" assert op is self.vm.frame.current_opcode if op.code.co_filename != self.vm.filename: return None, value if not op.annotation: return None, value annot = op.annotation frame = self.vm.frame var, errorlog = abstract_utils.eval_expr(self.vm, state.node, frame.f_globals, frame.f_locals, annot) if errorlog: self.vm.errorlog.invalid_annotation(self.vm.frames, annot, details=errorlog.details) typ = self.extract_annotation(state.node, var, name, self.vm.simple_stack(), is_var=True) _, value = self.vm.init_class(state.node, typ) for d in value.data: d.from_annotation = name return typ, value
def _eval_expr_as_tuple(self, node, f_globals, f_locals, expr): """Evaluate an expression as a tuple.""" if not expr: return () result = abstract_utils.get_atomic_value( abstract_utils.eval_expr(self.vm, node, f_globals, f_locals, expr)) # If the result is a tuple, expand it. if (isinstance(result, mixin.PythonConstant) and isinstance(result.pyval, tuple)): return tuple( abstract_utils.get_atomic_value(x) for x in result.pyval) else: return (result, )
def apply_annotation(self, state, op, name, value): """If there is an annotation for the op, return its value.""" assert op is self.vm.frame.current_opcode if op.code.co_filename != self.vm.filename: return None, value if not op.annotation: return None, value annot = op.annotation frame = self.vm.frame var, errorlog = abstract_utils.eval_expr( self.vm, state.node, frame.f_globals, frame.f_locals, annot) if errorlog: self.vm.errorlog.invalid_annotation( self.vm.frames, annot, details=errorlog.details) if frame.func and isinstance(frame.func.data, abstract.BoundFunction): self_var = frame.f_locals.pyval.get("self") if self_var: allowed_type_params = [] for v in self_var.data: if v.cls: allowed_type_params.extend(p.name for p in v.cls.template) else: allowed_type_params = () else: self_var = None allowed_type_params = self.vm.frame.type_params typ = self.extract_annotation( state.node, var, name, self.vm.simple_stack(), allowed_type_params=allowed_type_params) if typ.formal and self_var: type_params = self.get_type_parameters(typ) substs = [ abstract_utils.get_type_parameter_substitutions(v, type_params) for v in self_var.data] resolved_type = self.sub_one_annotation(state.node, typ, substs, instantiate_unbound=False) _, value = self.init_annotation(state.node, name, resolved_type) elif typ.formal: # The only time extract_annotation returns a formal type (i.e., one that # contains type parameters) is when we are in the body of a generic class. # Without `self_var`, we don't know what to instantiate those parameters # to. We set the value to empty to defer instantiation until the value is # being looked up on class instances, at which point the parameters will # have been filled in. value = self.vm.convert.empty.to_variable(state.node) else: _, value = self.init_annotation(state.node, name, typ) return typ, value
def apply_annotation(self, node, op, name, value): """If there is an annotation for the op, return its value.""" assert op is self.vm.frame.current_opcode if op.code.co_filename != self.vm.filename: return None, value if not op.annotation: return None, value annot = op.annotation frame = self.vm.frame var, errorlog = abstract_utils.eval_expr( self.vm, node, frame.f_globals, frame.f_locals, annot) if errorlog: self.vm.errorlog.invalid_annotation( self.vm.frames, annot, details=errorlog.details) return self.extract_and_init_annotation(node, name, var, use_not_supported_yet=True)
def _eval_expr_as_tuple(self, node, expr, stack): """Evaluate an expression as a tuple.""" if not expr: return (), None f_globals, f_locals = self.vm.frame.f_globals, self.vm.frame.f_locals with self.vm.generate_late_annotations(stack): result_var, errorlog = abstract_utils.eval_expr( self.vm, node, f_globals, f_locals, expr) result = abstract_utils.get_atomic_value(result_var) # If the result is a tuple, expand it. if (isinstance(result, mixin.PythonConstant) and isinstance(result.pyval, tuple)): return (tuple(abstract_utils.get_atomic_value(x) for x in result.pyval), errorlog) else: return (result,), errorlog
def apply_annotation(self, state, op, name, value): """If there is a type comment for the op, return its value.""" assert op is self.vm.frame.current_opcode if op.code.co_filename != self.vm.filename: return value if not op.annotation: return value annot = op.annotation frame = self.vm.frame var, errorlog = abstract_utils.eval_expr(self.vm, state.node, frame.f_globals, frame.f_locals, annot) if errorlog: self.vm.errorlog.invalid_annotation(self.vm.frames, annot, details=errorlog.details) try: typ = abstract_utils.get_atomic_value(var) except abstract_utils.ConversionError: self.vm.errorlog.invalid_annotation(self.vm.frames, annot, details="Must be constant.") value = self.vm.new_unsolvable(state.node) else: typ = self._process_one_annotation(state.node, typ, name, self.vm.simple_stack()) if typ: if self.get_type_parameters(typ): self.vm.errorlog.not_supported_yet( self.vm.frames, "using type parameter in variable annotation") value = self.vm.new_unsolvable(state.node) else: _, value = self.vm.init_class(state.node, typ) else: value = self.vm.new_unsolvable(state.node) return value
def _process_one_annotation(self, node, annotation, name, stack): """Change annotation / record errors where required.""" # Make sure we pass in a frozen snapshot of the frame stack, rather than the # actual stack, since late annotations need to snapshot the stack at time of # creation in order to get the right line information for error messages. assert isinstance(stack, tuple), "stack must be an immutable sequence" if isinstance(annotation, abstract.AnnotationContainer): annotation = annotation.base_cls if isinstance(annotation, typing_overlay.Union): self.vm.errorlog.invalid_annotation( stack, annotation, "Needs options", name) return None elif (name is not None and name != "return" and isinstance(annotation, typing_overlay.NoReturn)): self.vm.errorlog.invalid_annotation( stack, annotation, "NoReturn is not allowed", name) return None elif isinstance(annotation, abstract.Instance) and ( annotation.cls == self.vm.convert.str_type or annotation.cls == self.vm.convert.unicode_type ): # String annotations : Late evaluation if isinstance(annotation, mixin.PythonConstant): expr = annotation.pyval if not expr: self.vm.errorlog.invalid_annotation( stack, annotation, "Cannot be an empty string", name) return None frame = self.vm.frame # Immediately try to evaluate the reference, generating LateAnnotation # objects as needed. We don't store the entire string as a # LateAnnotation because: # - Starting in 3.8, or in 3.7 with __future__.annotations, all # annotations look like forward references - most of them don't need # to be late evaluated. # - Given an expression like "Union[str, NotYetDefined]", we want to # evaluate the union immediately so we don't end up with a complex # LateAnnotation, which can lead to bugs when instantiated. with self.vm.generate_late_annotations(stack): v, errorlog = abstract_utils.eval_expr( self.vm, node, frame.f_globals, frame.f_locals, expr) if errorlog: self.vm.errorlog.copy_from(errorlog.errors, stack) if len(v.data) == 1: return self._process_one_annotation(node, v.data[0], name, stack) self.vm.errorlog.ambiguous_annotation(stack, [annotation], name) return None elif annotation.cls == self.vm.convert.none_type: # PEP 484 allows to write "NoneType" as "None" return self.vm.convert.none_type elif isinstance(annotation, mixin.NestedAnnotation): if annotation.processed: return annotation annotation.processed = True for key, typ in annotation.get_inner_types(): processed = self._process_one_annotation(node, typ, name, stack) if processed is None: return None elif isinstance(processed, typing_overlay.NoReturn): self.vm.errorlog.invalid_annotation( stack, typ, "NoReturn is not allowed as inner type", name) return None annotation.update_inner_type(key, processed) return annotation elif isinstance(annotation, (class_mixin.Class, abstract.AMBIGUOUS_OR_EMPTY, abstract.TypeParameter, typing_overlay.NoReturn)): return annotation else: self.vm.errorlog.invalid_annotation(stack, annotation, "Not a type", name) return None
def _process_one_annotation(self, node, annotation, name, stack, seen=None): """Change annotation / record errors where required.""" # Make sure we pass in a frozen snapshot of the frame stack, rather than the # actual stack, since late annotations need to snapshot the stack at time of # creation in order to get the right line information for error messages. assert isinstance(stack, tuple), "stack must be an immutable sequence" # Check for recursive type annotations so we can emit an error message # rather than crashing. if seen is None: seen = set() if annotation.is_late_annotation(): if annotation in seen: self.vm.errorlog.not_supported_yet( stack, "Recursive type annotations", details="In annotation '%s' on %s" % (annotation.expr, name)) return None seen = seen | {annotation} if isinstance(annotation, abstract.AnnotationContainer): annotation = annotation.base_cls if isinstance(annotation, typing_overlay.Union): self.vm.errorlog.invalid_annotation(stack, annotation, "Needs options", name) return None elif (name is not None and name != "return" and isinstance(annotation, typing_overlay.NoReturn)): self.vm.errorlog.invalid_annotation(stack, annotation, "NoReturn is not allowed", name) return None elif isinstance(annotation, abstract.Instance) and ( annotation.cls == self.vm.convert.str_type or annotation.cls == self.vm.convert.unicode_type): # String annotations : Late evaluation if isinstance(annotation, mixin.PythonConstant): expr = annotation.pyval if not expr: self.vm.errorlog.invalid_annotation( stack, annotation, "Cannot be an empty string", name) return None frame = self.vm.frame # Immediately try to evaluate the reference, generating LateAnnotation # objects as needed. We don't store the entire string as a # LateAnnotation because: # - Starting in 3.8, or in 3.7 with __future__.annotations, all # annotations look like forward references - most of them don't need # to be late evaluated. # - Given an expression like "Union[str, NotYetDefined]", we want to # evaluate the union immediately so we don't end up with a complex # LateAnnotation, which can lead to bugs when instantiated. with self.vm.generate_late_annotations(stack): v, errorlog = abstract_utils.eval_expr( self.vm, node, frame.f_globals, frame.f_locals, expr) if errorlog: self.vm.errorlog.copy_from(errorlog.errors, stack) if len(v.data) == 1: return self._process_one_annotation( node, v.data[0], name, stack, seen) self.vm.errorlog.invalid_annotation(stack, annotation, "Must be constant", name) return None elif annotation.cls == self.vm.convert.none_type: # PEP 484 allows to write "NoneType" as "None" return self.vm.convert.none_type elif isinstance(annotation, abstract.ParameterizedClass): for param_name, param in annotation.formal_type_parameters.items(): processed = self._process_one_annotation( node, param, name, stack, seen) if processed is None: return None elif isinstance(processed, typing_overlay.NoReturn): self.vm.errorlog.invalid_annotation( stack, param, "NoReturn is not allowed as inner type", name) return None annotation.formal_type_parameters[param_name] = processed return annotation elif isinstance(annotation, abstract.Union): options = [] for option in annotation.options: processed = self._process_one_annotation( node, option, name, stack, seen) if processed is None: return None elif isinstance(processed, typing_overlay.NoReturn): self.vm.errorlog.invalid_annotation( stack, option, "NoReturn is not allowed as inner type", name) return None options.append(processed) annotation.options = tuple(options) return annotation elif isinstance(annotation, (mixin.Class, abstract.AMBIGUOUS_OR_EMPTY, abstract.TypeParameter, typing_overlay.NoReturn)): return annotation else: self.vm.errorlog.invalid_annotation(stack, annotation, "Not a type", name) return None
def _process_one_annotation(self, annotation, name, stack, node=None, f_globals=None, f_locals=None): """Change annotation / record errors where required.""" if isinstance(annotation, abstract.AnnotationContainer): annotation = annotation.base_cls if isinstance(annotation, typing_overlay.Union): self.vm.errorlog.invalid_annotation(stack, annotation, "Needs options", name) return None elif (name is not None and name != "return" and isinstance(annotation, typing_overlay.NoReturn)): self.vm.errorlog.invalid_annotation(stack, annotation, "NoReturn is not allowed", name) return None elif isinstance(annotation, abstract.Instance) and ( annotation.cls == self.vm.convert.str_type or annotation.cls == self.vm.convert.unicode_type): # String annotations : Late evaluation if isinstance(annotation, mixin.PythonConstant): if f_globals is None: raise self.LateAnnotationError() else: try: v = abstract_utils.eval_expr(self.vm, node, f_globals, f_locals, annotation.pyval) except abstract_utils.EvaluationError as e: self.vm.errorlog.invalid_annotation( stack, annotation, utils.message(e)) return None if len(v.data) == 1: return self._process_one_annotation( v.data[0], name, stack, node, f_globals, f_locals) self.vm.errorlog.invalid_annotation(stack, annotation, "Must be constant", name) return None elif annotation.cls == self.vm.convert.none_type: # PEP 484 allows to write "NoneType" as "None" return self.vm.convert.none_type elif isinstance(annotation, abstract.ParameterizedClass): for param_name, param in annotation.formal_type_parameters.items(): processed = self._process_one_annotation( param, name, stack, node, f_globals, f_locals) if processed is None: return None elif isinstance(processed, typing_overlay.NoReturn): self.vm.errorlog.invalid_annotation( stack, param, "NoReturn is not allowed as inner type", name) return None annotation.formal_type_parameters[param_name] = processed return annotation elif isinstance(annotation, abstract.Union): options = [] for option in annotation.options: processed = self._process_one_annotation( option, name, stack, node, f_globals, f_locals) if processed is None: return None elif isinstance(processed, typing_overlay.NoReturn): self.vm.errorlog.invalid_annotation( stack, option, "NoReturn is not allowed as inner type", name) return None options.append(processed) annotation.options = tuple(options) return annotation elif isinstance(annotation, (mixin.Class, abstract.AMBIGUOUS_OR_EMPTY, abstract.TypeParameter, typing_overlay.NoReturn)): return annotation else: self.vm.errorlog.invalid_annotation(stack, annotation, "Not a type", name) return None