diff --git a/pytype/abstract/_interpreter_function.py b/pytype/abstract/_interpreter_function.py index ce53abfcc..cfdb697ff 100644 --- a/pytype/abstract/_interpreter_function.py +++ b/pytype/abstract/_interpreter_function.py @@ -115,17 +115,18 @@ def make(cls, name, *, def_opcode, code, f_locals, f_globals, defaults, """ annotations = annotations or {} overloads = ctx.vm.frame.overloads[name] + if f_locals == ctx.convert.unsolvable: + local_members = {} + else: + local_members = f_locals.members key = (name, code, _hash_all_dicts( (f_globals.members, set(code.names)), - (f_locals.members, - set(f_locals.members) - set(code.varnames)), ({ - key: ctx.program.NewVariable([value], [], ctx.root_node) - for key, value in annotations.items() - }, None), (dict( - enumerate( - ctx.program.NewVariable([f], [], ctx.root_node) - for f in overloads)), None), + (local_members, set(local_members) - set(code.varnames)), + ({key: ctx.program.NewVariable([value], [], ctx.root_node) + for key, value in annotations.items()}, None), + (dict(enumerate(ctx.program.NewVariable([f], [], ctx.root_node) + for f in overloads)), None), (dict(enumerate(defaults)), None), (dict(enumerate(closure or ())), None))) if key not in cls._function_cache: @@ -382,11 +383,14 @@ def _hash_call(self, callargs, frame): if (self.ctx.options.skip_repeat_calls and ("self" not in callargs or not self.ctx.callself_stack or callargs["self"].data != self.ctx.callself_stack[-1].data)): + if frame.f_locals == self.ctx.convert.unsolvable: + local_members = {} + else: + local_members = frame.f_locals.members callkey = _hash_all_dicts( (callargs, None), (frame.f_globals.members, set(self.code.names)), - (frame.f_locals.members, - set(frame.f_locals.members) - set(self.code.varnames))) + (local_members, set(local_members) - set(self.code.varnames))) else: # Make the callkey the number of times this function has been called so # that no call has the same key as a previous one. diff --git a/pytype/block_environment.py b/pytype/block_environment.py index c352f5e03..ce2495f77 100644 --- a/pytype/block_environment.py +++ b/pytype/block_environment.py @@ -33,7 +33,11 @@ def add_block(self, frame, block): if b in self.block_locals and b != block and b not in self._dead_ends] n_inc = len(incoming) if n_inc == 0: - frame_locals = {k: [v] for k, v in frame.f_locals.pyval.items()} + try: + f_locals = frame.f_locals.pyval.items() + except AttributeError: + f_locals = () + frame_locals = {k: [v] for k, v in f_locals} local.update(frame_locals) elif n_inc == 1: inc, = incoming diff --git a/pytype/compare.py b/pytype/compare.py index 825ff116e..6f1c2e151 100644 --- a/pytype/compare.py +++ b/pytype/compare.py @@ -38,7 +38,7 @@ def _is_primitive(ctx, value): if _is_primitive_constant(ctx, value): return True elif isinstance(value, abstract.Instance): - return value.full_name in ctx.convert.primitive_class_names + return value.full_name in ctx.convert.primitive_classes_by_name return False diff --git a/pytype/convert.py b/pytype/convert.py index d938dacd7..976f8dfb1 100644 --- a/pytype/convert.py +++ b/pytype/convert.py @@ -94,8 +94,8 @@ def __init__(self, ctx): self.primitive_classes = { v: self.constant_to_value(v) for v in primitive_classes } - self.primitive_class_names = [ - ".".join(self._type_to_name(x)) for x in self.primitive_classes] + self.primitive_classes_by_name = { + ".".join(self._type_to_name(x)): x for x in self.primitive_classes} self.none = abstract.ConcreteValue(None, self.primitive_classes[NoneType], self.ctx) self.true = abstract.ConcreteValue(True, self.primitive_classes[bool], @@ -930,6 +930,10 @@ def _constant_to_value(self, pyval, subst, get_node): mycls = self.constant_to_value(cls, subst, self.ctx.root_node) if isinstance(mycls, typed_dict.TypedDictClass): instance = mycls.instantiate_value(self.ctx.root_node, None) + elif (isinstance(mycls, abstract.PyTDClass) and + mycls.pytd_cls.name in self.primitive_classes_by_name): + instance = self.primitive_class_instances[ + self.primitive_classes_by_name[mycls.pytd_cls.name]] else: instance = abstract.Instance(mycls, self.ctx) log.info("New pytd instance for %s: %r", cls.name, instance) diff --git a/pytype/pyi/parser_test.py b/pytype/pyi/parser_test.py index fa55f7e3b..7a3e4a3f2 100644 --- a/pytype/pyi/parser_test.py +++ b/pytype/pyi/parser_test.py @@ -2017,13 +2017,19 @@ def test_property_decorator_bad_syntax(self): class A: @property def name(self, bad_arg): ... - """, 1, "@property needs 1 param(s), got 2") + """, 1, "@property must have 1 param(s), but actually has 2") self.check_error(""" class A: @name.setter def name(self): ... - """, 1, "@name.setter needs 2 param(s), got 1") + """, 1, "@name.setter must have 2 param(s), but actually has 1") + + self.check(""" + class A: + @property + def name(self, optional_arg: str = ...): ... + """, expected=parser_test_base.IGNORE) self.check_error(""" class A: diff --git a/pytype/pytd/codegen/function.py b/pytype/pytd/codegen/function.py index 7dead87fa..350063d1e 100644 --- a/pytype/pytd/codegen/function.py +++ b/pytype/pytd/codegen/function.py @@ -232,12 +232,15 @@ def __post_init__(self): def add_property(self, decorator, sig): prop = self.prop_names[decorator] - if prop.arity == len(sig.params): + if prop.arity == len([s for s in sig.params if not s.optional]): assert self.properties is not None self.properties.set(prop.type, sig, self.name) else: - raise TypeError("Property decorator @%s needs %d param(s), got %d" % - (decorator, prop.arity, len(sig.params))) + raise TypeError( + f"Function '{self.name}' decorated by property decorator" + f" @{decorator} must have {prop.arity} param(s), but actually has" + f" {len(sig.params)}" + ) def add_overload(self, fn: NameAndSig): """Add an overloaded signature to a function.""" diff --git a/pytype/tests/test_splits2.py b/pytype/tests/test_splits2.py index 2a351192f..557379d02 100644 --- a/pytype/tests/test_splits2.py +++ b/pytype/tests/test_splits2.py @@ -451,6 +451,16 @@ def Build(self) -> List[str]: return a.Get() """) + def test_frametype(self): + self.Check(""" + import inspect + current = inspect.currentframe() + assert current is not None + caller = current.f_back + assert caller is not None + code = caller.f_code + """) + if __name__ == "__main__": test_base.main() diff --git a/pytype/vm.py b/pytype/vm.py index ffb95a322..f6819260d 100644 --- a/pytype/vm.py +++ b/pytype/vm.py @@ -770,11 +770,15 @@ def load_from( name: str, discard_concrete_values: bool = False ) -> Tuple[frame_state.FrameState, cfg.Variable]: """Load an item out of locals, globals, or builtins.""" - store.load_lazy_attribute(name) - try: - member = store.members[name] - except KeyError: - return state, self._load_annotation(state.node, name, store) + if isinstance(store, mixin.LazyMembers): + store.load_lazy_attribute(name) + try: + member = store.members[name] + except KeyError: + return state, self._load_annotation(state.node, name, store) + else: + assert store == self.ctx.convert.unsolvable + return state, self.ctx.new_unsolvable(state.node) bindings = member.Bindings(state.node) if (not bindings and self._late_annotations_stack and member.bindings and all(isinstance(v, abstract.Module) for v in member.data)):