Skip to content

[mypyc] Detect always defined attributes #12600

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

Merged
merged 100 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
7f3c385
[mypyc] Make BaseAnalysisVisitor generic
JukkaL Aug 7, 2021
d3e17ba
[WIP] Start work on always defined attributes analysis
JukkaL May 8, 2021
7c7db8e
Fix issues caused by rebase
JukkaL Aug 7, 2021
89c65cf
Add missing import
JukkaL Aug 7, 2021
79d2dd4
Fixed to arbitrary execution visitor
JukkaL Aug 7, 2021
9ba9a77
Add missing cases to visitor
JukkaL Aug 7, 2021
05f596b
Remove attr initialization detection from main ir build phase
JukkaL Aug 7, 2021
f3d55e0
Revert more changes
JukkaL Aug 7, 2021
41c7c9e
Add attribute defined implementation
JukkaL Aug 7, 2021
31e413c
Remove unused imports
JukkaL Aug 7, 2021
8540685
Partial implementation of always defined attr analysis using IR
JukkaL Aug 7, 2021
c329c6f
Fix some special cases
JukkaL Aug 7, 2021
64bab0a
[mypyc] Keep track of functions that won't run arbitrary code
JukkaL Aug 7, 2021
32e4cab
Functions that can't run arbitrary code are fine
JukkaL Aug 7, 2021
70c18a7
Add docstring
JukkaL Aug 7, 2021
129ec0d
Remove original attempt that worked on mypy ATSs
JukkaL Aug 7, 2021
4ee1558
Detect initialization-only setattr ops
JukkaL Aug 7, 2021
cd2132a
Fix initialization of always defined attributes
JukkaL Aug 7, 2021
08262f1
Add test cases
JukkaL Aug 7, 2021
f9a9741
Add test cases
JukkaL Aug 7, 2021
181a334
Add comment
JukkaL Aug 7, 2021
b9b601b
Fix asserts
JukkaL Aug 7, 2021
4b385d4
Refactor + add docstrings and comments
JukkaL Aug 14, 2021
d2fe861
Record which attributes have default values in class body
JukkaL Aug 14, 2021
ae2568c
Treat attributes initialized in class body as always defined
JukkaL Aug 14, 2021
b076a42
Add IR build test case for attribute default
JukkaL Aug 14, 2021
3194d92
Add tests
JukkaL Aug 14, 2021
d79baad
Fix issues caused by rebase
JukkaL Jan 1, 2022
37e5508
Update singledispatch test cases
JukkaL Jan 1, 2022
ef38388
Analyze always defined attributes across class hierarchy
JukkaL Aug 14, 2021
92e7eb9
Fix multiple inheritance levels in hierarchy
JukkaL Aug 14, 2021
6cf694c
Add tests
JukkaL Aug 14, 2021
789e63f
DEBUG PRINT
JukkaL Aug 14, 2021
7aff66e
Update failing irbuild tests
JukkaL Jan 1, 2022
025fca5
Fix class IR serialization
JukkaL Jan 1, 2022
cded5cd
Fix serialization
JukkaL Jan 2, 2022
27482a0
Fix attributes initialized like "x = None # type: str"
JukkaL Jan 2, 2022
6b411c3
Fix dataclass and attrs defaults
JukkaL Jan 2, 2022
1ebf170
Update test case
JukkaL Jan 2, 2022
5cfb7f9
Improve docstrings
JukkaL Jan 2, 2022
bf4a664
Unrelated: remove obsolete comment
JukkaL Jan 2, 2022
6b49963
Also consider "dirty" transitions through branches and gotos
JukkaL Jan 2, 2022
1ae5b54
List comprehensions don't run arbitrary code
JukkaL Jan 2, 2022
fafec1d
Refactor test case
JukkaL Jan 2, 2022
70395d0
Deletable attributes can't be always defined
JukkaL Jan 2, 2022
4963ca3
Derived class may affect always defined status in base
JukkaL Jan 2, 2022
e28dfaa
Avoid generating branches for always defined attribute gets
JukkaL Jan 2, 2022
b30e532
Add comment
JukkaL Jan 3, 2022
6b3e98e
Accept undefined values in setter
JukkaL Jan 3, 2022
9a1c679
WIP docstring update (no correnponding implementation change)
JukkaL Jan 11, 2022
e58fb36
Fix after rebase
JukkaL Jan 14, 2022
7b1ac7a
Fix lint
JukkaL Jan 15, 2022
cc9615e
Use self leaks instead of running arbitrary code in analysis
JukkaL Jan 15, 2022
0bd7926
Always check for uninitialized values in CPython getter
JukkaL Jan 15, 2022
1d50647
Implement Type shallow copy visitor to avoid copy.copy()
JukkaL Jan 15, 2022
a7d909b
Fix issues with new __new__ semantics
JukkaL Jan 15, 2022
2f595be
Add del attr test case
JukkaL Jan 30, 2022
4ff3fc0
Add more inheritance tests
JukkaL Jan 30, 2022
b4e32dc
Add some simple trait tests
JukkaL Jan 30, 2022
fc9c657
Test with allow_interpreted_subclasses
JukkaL Jan 30, 2022
a7a66f7
Test additional kinds of expressions
JukkaL Jan 30, 2022
23eacc8
Add test cases
JukkaL Jan 30, 2022
d94963a
Fixes to properties
JukkaL Jan 30, 2022
16c59cc
Fix handling of self in assignments
JukkaL Jan 30, 2022
c62b486
Docstring updates
JukkaL Feb 20, 2022
4ddfe84
Update try statement test case
JukkaL Feb 20, 2022
ac0761d
Add try/finally test case
JukkaL Feb 20, 2022
69d15e0
Add with test case
JukkaL Feb 20, 2022
41efa34
Add test case
JukkaL Feb 20, 2022
093fcf3
Rename defined -> selfleaks
JukkaL Feb 20, 2022
0659f59
Add test case for Python base class
JukkaL Feb 20, 2022
3fcec57
Add incremental mode test case
JukkaL Feb 20, 2022
18534fb
Improve documentation of tests for multi-file and separate modes
JukkaL Feb 20, 2022
1539ad2
Comment out debug print
JukkaL Feb 20, 2022
cbb5f37
Fix after rebase
JukkaL Mar 26, 2022
709f29e
Fix after rebase
JukkaL Apr 15, 2022
e494eb2
Fix copying of ParamSpecType
JukkaL Apr 15, 2022
42764d1
Update docstring
JukkaL Apr 15, 2022
c575d44
Support copying Parameters
JukkaL Apr 15, 2022
3c3ad7a
Clean up
JukkaL Apr 15, 2022
28ba726
Remove another use of copy.copy()
JukkaL Apr 15, 2022
335fc21
Fix crash during compilation
JukkaL Apr 15, 2022
53eb2cc
Fix stubgen test failures
JukkaL Apr 15, 2022
8371b60
Simplify a bit
JukkaL Apr 16, 2022
e83cc7c
Improve test case
JukkaL Apr 16, 2022
5aa0fef
Update comments
JukkaL Apr 16, 2022
47d25d3
Drop run arbitrary code flags
JukkaL Apr 16, 2022
8d1dca2
Kepe track of sometimes initialized attributes in __init__
JukkaL Apr 16, 2022
3d96774
Fix lint
JukkaL Apr 16, 2022
20861b9
Fix syntax on older Python versions
JukkaL Apr 16, 2022
2477704
Merge branch 'master' into always-defined-inheritance-5
JukkaL May 15, 2022
96e99d6
Support TypeVarTupleType
JukkaL May 15, 2022
fed8ecd
Address some feedback
JukkaL May 15, 2022
53caf2b
Rename init_unknown_code to init_self_leak
JukkaL May 15, 2022
8e371a7
Update comments based on feedback
JukkaL May 15, 2022
dd436fb
Adjust GenAndKill based on feedback
JukkaL May 15, 2022
e409f5d
Allow supporting pickle/copy by using @mypyc_attr(serialize=True)
JukkaL May 15, 2022
d3cc68b
Document the serializable flag
JukkaL May 15, 2022
49efa36
Fix example
JukkaL May 15, 2022
51e23b0
Address feedback
JukkaL May 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions mypy/copytype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from typing import Any, cast

from mypy.types import (
ProperType, UnboundType, AnyType, NoneType, UninhabitedType, ErasedType, DeletedType,
Instance, TypeVarType, ParamSpecType, PartialType, CallableType, TupleType, TypedDictType,
LiteralType, UnionType, Overloaded, TypeType, TypeAliasType, UnpackType, Parameters,
TypeVarTupleType
)
from mypy.type_visitor import TypeVisitor


def copy_type(t: ProperType) -> ProperType:
"""Create a shallow copy of a type.

This can be used to mutate the copy with truthiness information.

Classes compiled with mypyc don't support copy.copy(), so we need
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not supporting copy.copy is pretty rough. This will also mean that we don't support pickle, right?

I don't think that we can reasonably break those without providing an opt-opt.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I was planning to provide a way to do this later, but I can add it to this PR. We could have a per-class flag saying that this class (and any subclasses, probably) is picklable, and no attribute would be treated as always defined in the class, and thus we can always provide a way of creating an uninitialized instance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it's supported by using @mypyc_attr(serializable=True).

a custom implementation.
"""
return t.accept(TypeShallowCopier())


class TypeShallowCopier(TypeVisitor[ProperType]):
def visit_unbound_type(self, t: UnboundType) -> ProperType:
return t

def visit_any(self, t: AnyType) -> ProperType:
return self.copy_common(t, AnyType(t.type_of_any, t.source_any, t.missing_import_name))

def visit_none_type(self, t: NoneType) -> ProperType:
return self.copy_common(t, NoneType())

def visit_uninhabited_type(self, t: UninhabitedType) -> ProperType:
dup = UninhabitedType(t.is_noreturn)
dup.ambiguous = t.ambiguous
return self.copy_common(t, dup)

def visit_erased_type(self, t: ErasedType) -> ProperType:
return self.copy_common(t, ErasedType())

def visit_deleted_type(self, t: DeletedType) -> ProperType:
return self.copy_common(t, DeletedType(t.source))

def visit_instance(self, t: Instance) -> ProperType:
dup = Instance(t.type, t.args, last_known_value=t.last_known_value)
dup.invalid = t.invalid
return self.copy_common(t, dup)

def visit_type_var(self, t: TypeVarType) -> ProperType:
dup = TypeVarType(
t.name,
t.fullname,
t.id,
values=t.values,
upper_bound=t.upper_bound,
variance=t.variance,
)
return self.copy_common(t, dup)

def visit_param_spec(self, t: ParamSpecType) -> ProperType:
dup = ParamSpecType(t.name, t.fullname, t.id, t.flavor, t.upper_bound, prefix=t.prefix)
return self.copy_common(t, dup)

def visit_parameters(self, t: Parameters) -> ProperType:
dup = Parameters(t.arg_types, t.arg_kinds, t.arg_names,
variables=t.variables,
is_ellipsis_args=t.is_ellipsis_args)
return self.copy_common(t, dup)

def visit_type_var_tuple(self, t: TypeVarTupleType) -> ProperType:
dup = TypeVarTupleType(t.name, t.fullname, t.id, t.upper_bound)
return self.copy_common(t, dup)

def visit_unpack_type(self, t: UnpackType) -> ProperType:
dup = UnpackType(t.type)
return self.copy_common(t, dup)

def visit_partial_type(self, t: PartialType) -> ProperType:
return self.copy_common(t, PartialType(t.type, t.var, t.value_type))

def visit_callable_type(self, t: CallableType) -> ProperType:
return self.copy_common(t, t.copy_modified())

def visit_tuple_type(self, t: TupleType) -> ProperType:
return self.copy_common(t, TupleType(t.items, t.partial_fallback, implicit=t.implicit))

def visit_typeddict_type(self, t: TypedDictType) -> ProperType:
return self.copy_common(t, TypedDictType(t.items, t.required_keys, t.fallback))

def visit_literal_type(self, t: LiteralType) -> ProperType:
return self.copy_common(t, LiteralType(value=t.value, fallback=t.fallback))

def visit_union_type(self, t: UnionType) -> ProperType:
return self.copy_common(t, UnionType(t.items))

def visit_overloaded(self, t: Overloaded) -> ProperType:
return self.copy_common(t, Overloaded(items=t.items))

def visit_type_type(self, t: TypeType) -> ProperType:
# Use cast since the type annotations in TypeType are imprecise.
return self.copy_common(t, TypeType(cast(Any, t.item)))

def visit_type_alias_type(self, t: TypeAliasType) -> ProperType:
assert False, "only ProperTypes supported"

def copy_common(self, t: ProperType, t2: ProperType) -> ProperType:
t2.line = t.line
t2.column = t.column
t2.can_be_false = t.can_be_false
t2.can_be_true = t.can_be_true
return t2
15 changes: 8 additions & 7 deletions mypy/moduleinspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@


class ModuleProperties:
# Note that all __init__ args must have default values
def __init__(self,
name: str,
file: Optional[str],
path: Optional[List[str]],
all: Optional[List[str]],
is_c_module: bool,
subpackages: List[str]) -> None:
name: str = "",
file: Optional[str] = None,
path: Optional[List[str]] = None,
all: Optional[List[str]] = None,
is_c_module: bool = False,
subpackages: Optional[List[str]] = None) -> None:
self.name = name # __name__ attribute
self.file = file # __file__ attribute
self.path = path # __path__ attribute
self.all = all # __all__ attribute
self.is_c_module = is_c_module
self.subpackages = subpackages
self.subpackages = subpackages or []


def is_c_module(module: ModuleType) -> bool:
Expand Down
17 changes: 9 additions & 8 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,16 +668,16 @@ class FuncItem(FuncBase):
__deletable__ = ('arguments', 'max_pos', 'min_args')

def __init__(self,
arguments: List[Argument],
body: 'Block',
arguments: Optional[List[Argument]] = None,
body: Optional['Block'] = None,
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
super().__init__()
self.arguments = arguments
self.arg_names = [None if arg.pos_only else arg.variable.name for arg in arguments]
self.arguments = arguments or []
self.arg_names = [None if arg.pos_only else arg.variable.name for arg in self.arguments]
self.arg_kinds: List[ArgKind] = [arg.kind for arg in self.arguments]
self.max_pos: int = (
self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT))
self.body: 'Block' = body
self.body: 'Block' = body or Block([])
self.type = typ
self.unanalyzed_type = typ
self.is_overload: bool = False
Expand Down Expand Up @@ -725,10 +725,11 @@ class FuncDef(FuncItem, SymbolNode, Statement):
'original_def',
)

# Note that all __init__ args must have default values
def __init__(self,
name: str, # Function name
arguments: List[Argument],
body: 'Block',
name: str = '', # Function name
arguments: Optional[List[Argument]] = None,
body: Optional['Block'] = None,
typ: 'Optional[mypy.types.FunctionLike]' = None) -> None:
super().__init__(arguments, body, typ)
self._name = name
Expand Down
2 changes: 1 addition & 1 deletion mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,6 @@ def _resolve_funcitem_from_decorator(dec: nodes.OverloadPart) -> Optional[nodes.

Returns None if we can't figure out what that would be. For convenience, this function also
accepts FuncItems.

"""
if isinstance(dec, nodes.FuncItem):
return dec
Expand All @@ -917,6 +916,7 @@ def apply_decorator_to_funcitem(
return func
if decorator.fullname == "builtins.classmethod":
assert func.arguments[0].variable.name in ("cls", "metacls")
# FuncItem is written so that copy.copy() actually works, even when compiled
ret = copy.copy(func)
# Remove the cls argument, since it's not present in inspect.signature of classmethods
ret.arguments = ret.arguments[1:]
Expand Down
4 changes: 2 additions & 2 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarLikeType, Overloaded,
TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType,
AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types,
copy_type, TypeAliasType, TypeQuery, ParamSpecType, Parameters,
ENUM_REMOVED_PROPS
TypeAliasType, TypeQuery, ParamSpecType, Parameters, ENUM_REMOVED_PROPS
)
from mypy.nodes import (
FuncBase, FuncItem, FuncDef, OverloadedFuncDef, TypeInfo, ARG_STAR, ARG_STAR2, ARG_POS,
Expression, StrExpr, Var, Decorator, SYMBOL_FUNCBASE_TYPES
)
from mypy.maptype import map_instance_to_supertype
from mypy.expandtype import expand_type_by_instance, expand_type
from mypy.copytype import copy_type

from mypy.typevars import fill_typevars

Expand Down
11 changes: 0 additions & 11 deletions mypy/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Classes for representing mypy types."""

import copy
import sys
from abc import abstractmethod

Expand Down Expand Up @@ -2893,16 +2892,6 @@ def is_named_instance(t: Type, fullnames: Union[str, Tuple[str, ...]]) -> bool:
return isinstance(t, Instance) and t.type.fullname in fullnames


TP = TypeVar('TP', bound=Type)


def copy_type(t: TP) -> TP:
"""
Build a copy of the type; used to mutate the copy with truthiness information
"""
return copy.copy(t)


class InstantiateAliasVisitor(TypeTranslator):
def __init__(self, vars: List[str], subs: List[Type]) -> None:
self.replacements = {v: s for (v, s) in zip(vars, subs)}
Expand Down
Loading