Skip to content
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

Google sync #1040

Merged
merged 14 commits into from Nov 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG
@@ -1,3 +1,19 @@
Version 2021.11.02:

New features and updates:
* Remove the --bind-properties flag. Its behavior has been made the default.
* Take advantage of module aliases to print prettier stub files.
* Add support for cross-module attr.s wrappers.
* Add a feature flag, --gen-stub-imports, to improve pyi import handling.
* Add a bit more support for PEP 612 in stubs.

Bug fixes:
* Add remove{prefix,suffix} methods for bytes, bytearray.
* Fix a bug where Errorlog.copy_from() duplicated error details.
* Fix some issues with handling module aliases in stub files.
* Support a [not-supported-yet] case in a generic class TypeVar renaming check.
* Add `__init__` attributes to canonical enum members.

Version 2021.10.25:

New features and updates:
Expand Down
2 changes: 1 addition & 1 deletion pytype/__version__.py
@@ -1,2 +1,2 @@
# pylint: skip-file
__version__ = '2021.10.25'
__version__ = '2021.11.02'
45 changes: 29 additions & 16 deletions pytype/abstract/abstract.py
Expand Up @@ -1557,23 +1557,36 @@ def _build_value(self, node, inner, ellipses):
# For user-defined generic types, check if its type parameter matches
# its corresponding concrete type
if isinstance(base_cls, InterpreterClass) and base_cls.template:
for formal in base_cls.template:
if (isinstance(formal, TypeParameter) and not formal.is_generic() and
isinstance(params[formal.name], TypeParameter)):
if formal.name != params[formal.name].name:
self.ctx.errorlog.not_supported_yet(
self.ctx.vm.frames,
"Renaming TypeVar `%s` with constraints or bound" % formal.name)
for formal_param in base_cls.template:
root_node = self.ctx.root_node
param_value = params[formal_param.name]
if (isinstance(formal_param, TypeParameter) and
not formal_param.is_generic() and
isinstance(param_value, TypeParameter)):
if formal_param.name == param_value.name:
# We don't need to check if a TypeParameter matches itself.
continue
else:
actual = param_value.instantiate(
root_node, container=abstract_utils.DUMMY_CONTAINER)
else:
root_node = self.ctx.root_node
actual = params[formal.name].instantiate(root_node)
bad = self.ctx.matcher(root_node).bad_matches(actual, formal)
if bad:
formal = self.ctx.annotation_utils.sub_one_annotation(
root_node, formal, [{}])
self.ctx.errorlog.bad_concrete_type(self.ctx.vm.frames, root_node,
formal, actual, bad)
return self.ctx.convert.unsolvable
actual = param_value.instantiate(root_node)
bad = self.ctx.matcher(root_node).bad_matches(actual, formal_param)
if bad:
if not isinstance(param_value, TypeParameter):
# If param_value is not a TypeVar, we substitute in TypeVar bounds
# and constraints in formal_param for a more helpful error message.
formal_param = self.ctx.annotation_utils.sub_one_annotation(
root_node, formal_param, [{}])
details = None
elif isinstance(formal_param, TypeParameter):
details = (f"TypeVars {formal_param.name} and {param_value.name} "
"have incompatible bounds or constraints.")
else:
details = None
self.ctx.errorlog.bad_concrete_type(
self.ctx.vm.frames, root_node, formal_param, actual, bad, details)
return self.ctx.convert.unsolvable

try:
return abstract_class(base_cls, params, self.ctx, template_params)
Expand Down
3 changes: 2 additions & 1 deletion pytype/analyze.py
Expand Up @@ -106,7 +106,8 @@ def infer_types(src,
if ctx.vm.has_unknown_wildcard_imports or any(
a in defs for a in abstract_utils.DYNAMIC_ATTRIBUTE_MARKERS):
if "__getattr__" not in ast:
ast = pytd_utils.Concat(ast, builtins.GetDefaultAst())
ast = pytd_utils.Concat(
ast, builtins.GetDefaultAst(options.gen_stub_imports))
# If merged with other if statement, triggers a ValueError: Unresolved class
# when attempts to load from the protocols file
if options.protocols:
Expand Down
5 changes: 5 additions & 0 deletions pytype/config.py
Expand Up @@ -172,6 +172,11 @@ def add_basic_options(o):
help=(
"Enable stricter namedtuple checks, such as unpacking and "
"'typing.Tuple' compatibility. ") + temporary)
o.add_argument(
"--gen-stub-imports", action="store_true",
dest="gen_stub_imports", default=False,
help=("Generate import statements (`import x`) rather than constants "
"(`x: module`) for module names in stub files. ") + temporary)


def add_subtools(o):
Expand Down
32 changes: 20 additions & 12 deletions pytype/convert.py
Expand Up @@ -514,20 +514,25 @@ def _load_late_type_module(self, late_type):
def _load_late_type(self, late_type):
"""Resolve a late type, possibly by loading a module."""
if late_type.name not in self._resolved_late_types:
ast, attr_name = self._load_late_type_module(late_type)
if ast is None:
log.error("During dependency resolution, couldn't resolve late type %r",
late_type.name)
t = pytd.AnythingType()
ast = self.ctx.loader.import_name(late_type.name)
if ast:
t = pytd.Module(name=late_type.name, module_name=late_type.name)
else:
try:
cls = pytd.LookupItemRecursive(ast, attr_name)
except KeyError:
if "__getattr__" not in ast:
log.warning("Couldn't resolve %s", late_type.name)
ast, attr_name = self._load_late_type_module(late_type)
if ast is None:
log.error(
"During dependency resolution, couldn't resolve late type %r",
late_type.name)
t = pytd.AnythingType()
else:
t = pytd.ToType(cls, allow_functions=True)
try:
cls = pytd.LookupItemRecursive(ast, attr_name)
except KeyError:
if "__getattr__" not in ast:
log.warning("Couldn't resolve %s", late_type.name)
t = pytd.AnythingType()
else:
t = pytd.ToType(cls, allow_functions=True)
self._resolved_late_types[late_type.name] = t
return self._resolved_late_types[late_type.name]

Expand All @@ -536,7 +541,10 @@ def _create_module(self, ast):
raise abstract_utils.ModuleLoadError()
data = (ast.constants + ast.type_params + ast.classes +
ast.functions + ast.aliases)
members = {val.name.rsplit(".")[-1]: val for val in data}
members = {}
for val in data:
name = utils.strip_prefix(val.name, f"{ast.name}.")
members[name] = val
return abstract.Module(self.ctx, ast.name, members, ast)

def _get_literal_value(self, pyval):
Expand Down
14 changes: 8 additions & 6 deletions pytype/errors.py
Expand Up @@ -347,7 +347,7 @@ def __getitem__(self, index):
def copy_from(self, errors, stack):
for e in errors:
with _CURRENT_ERROR_NAME.bind(e.name):
self.error(stack, e.message, e.details, e.keyword, e.bad_call,
self.error(stack, e._message, e.details, e.keyword, e.bad_call, # pylint: disable=protected-access
e.keyword_context)

def is_valid_error_name(self, name):
Expand Down Expand Up @@ -923,14 +923,16 @@ def bad_yield_annotation(self, stack, name, annot, is_async):
self.error(stack, message, details)

@_error_name("bad-concrete-type")
def bad_concrete_type(self, stack, node, formal, actual, bad):
def bad_concrete_type(self, stack, node, formal, actual, bad, details=None):
expected, actual, _, protocol_details, nis_details = (
self._print_as_return_types(node, formal, actual, bad))
details = [" Expected: ", expected, "\n",
"Actually passed: ", actual]
details.extend(protocol_details + nis_details)
full_details = [" Expected: ", expected, "\n",
"Actually passed: ", actual]
if details:
full_details.append("\n" + details)
full_details.extend(protocol_details + nis_details)
self.error(
stack, "Invalid instantiation of generic class", "".join(details))
stack, "Invalid instantiation of generic class", "".join(full_details))

def _show_variable(self, var):
"""Show variable as 'name: typ' or 'pyval: typ' if available."""
Expand Down
4 changes: 2 additions & 2 deletions pytype/io.py
Expand Up @@ -136,7 +136,7 @@ def check_or_generate_pyi(options, loader=None):

errorlog = errors.ErrorLog()
result = pytd_builtins.DEFAULT_SRC
ast = pytd_builtins.GetDefaultAst()
ast = pytd_builtins.GetDefaultAst(options.gen_stub_imports)
try:
src = read_source_file(options.input, options.open_function)
if options.check:
Expand Down Expand Up @@ -235,7 +235,7 @@ def write_pickle(ast, options, loader=None):
if options.nofail:
ast = serialize_ast.PrepareForExport(
options.module_name,
pytd_builtins.GetDefaultAst(), loader)
pytd_builtins.GetDefaultAst(options.gen_stub_imports), loader)
log.warning("***Caught exception: %s", str(e), exc_info=True)
else:
raise
Expand Down
6 changes: 4 additions & 2 deletions pytype/io_test.py
Expand Up @@ -80,9 +80,11 @@ def test_generate_pyi_with_options(self):
with self._tmpfile(
"{mod} {path}".format(mod=pyi_name, path=pyi.name)) as imports_map:
src = "import {mod}; y = {mod}.x".format(mod=pyi_name)
options = config.Options.create(imports_map=imports_map.name)
options = config.Options.create(imports_map=imports_map.name,
gen_stub_imports=True)
_, pyi_string, _ = io.generate_pyi(src, options)
self.assertEqual(pyi_string, "{mod}: module\ny: int\n".format(mod=pyi_name))
self.assertEqual(pyi_string,
"import {mod}\n\ny: int\n".format(mod=pyi_name))

def test_check_or_generate_pyi__check(self):
with self._tmpfile("") as f:
Expand Down
26 changes: 17 additions & 9 deletions pytype/load_pytd.py
Expand Up @@ -27,6 +27,7 @@
"python_version": "python_version",
"pythonpath": "pythonpath",
"use_typeshed": "typeshed",
"gen_stub_imports": "gen_stub_imports",
}


Expand Down Expand Up @@ -129,8 +130,9 @@ class _ModuleMap:

PREFIX = "pytd:" # for pytd files that ship with pytype

def __init__(self, python_version, modules=None):
def __init__(self, python_version, modules, gen_stub_imports):
self.python_version = python_version
self.gen_stub_imports = gen_stub_imports
self._modules: Dict[str, Module] = modules or self._base_modules()
if self._modules["builtins"].needs_unpickling():
self._unpickle_module(self._modules["builtins"])
Expand Down Expand Up @@ -186,7 +188,7 @@ def get_resolved_modules(self) -> Dict[str, ResolvedModule]:
return resolved_modules

def _base_modules(self):
bltins, typing = builtins.GetBuiltinsAndTyping()
bltins, typing = builtins.GetBuiltinsAndTyping(self.gen_stub_imports)
return {
"builtins":
Module("builtins", self.PREFIX + "builtins", bltins,
Expand Down Expand Up @@ -371,8 +373,9 @@ def collect_dependencies(cls, mod_ast):
class _BuiltinLoader:
"""Load builtins from the pytype source tree."""

def __init__(self, python_version):
def __init__(self, python_version, gen_stub_imports):
self.python_version = python_version
self.gen_stub_imports = gen_stub_imports

def _parse_predefined(self, pytd_subdir, module, as_package=False):
"""Parse a pyi/pytd file in the pytype source tree."""
Expand All @@ -382,7 +385,8 @@ def _parse_predefined(self, pytd_subdir, module, as_package=False):
except IOError:
return None
ast = parser.parse_string(src, filename=filename, name=module,
python_version=self.python_version)
python_version=self.python_version,
gen_stub_imports=self.gen_stub_imports)
assert ast.name == module
return ast

Expand Down Expand Up @@ -415,6 +419,7 @@ class Loader:
imports_map: A short_path -> full_name mapping for imports.
use_typeshed: Whether to use https://github.com/python/typeshed.
open_function: A custom file opening function.
gen_stub_imports: Temporary flag for releasing --gen-stub-imports.
"""

PREFIX = "pytd:" # for pytd files that ship with pytype
Expand All @@ -426,20 +431,22 @@ def __init__(self,
imports_map=None,
use_typeshed=True,
modules=None,
open_function=open):
open_function=open,
gen_stub_imports=True):
self.python_version = utils.normalize_version(python_version)
self._modules = _ModuleMap(self.python_version, modules)
self._modules = _ModuleMap(self.python_version, modules, gen_stub_imports)
self.builtins = self._modules["builtins"].ast
self.typing = self._modules["typing"].ast
self.base_module = base_module
self._path_finder = _PathFinder(imports_map, pythonpath)
self._builtin_loader = _BuiltinLoader(self.python_version)
self._builtin_loader = _BuiltinLoader(self.python_version, gen_stub_imports)
self._resolver = _Resolver(self.builtins)
self.use_typeshed = use_typeshed
self.open_function = open_function
self._import_name_cache = {} # performance cache
self._aliases = {}
self._prefixes = set()
self.gen_stub_imports = gen_stub_imports
# Paranoid verification that pytype.main properly checked the flags:
if imports_map is not None:
assert pythonpath == [""], pythonpath
Expand Down Expand Up @@ -505,7 +512,8 @@ def load_file(self, module_name, filename, mod_ast=None):
with self.open_function(filename, "r") as f:
mod_ast = parser.parse_string(
f.read(), filename=filename, name=module_name,
python_version=self.python_version)
python_version=self.python_version,
gen_stub_imports=self.gen_stub_imports)
return self._process_module(module_name, filename, mod_ast)

def _process_module(self, module_name, filename, mod_ast):
Expand Down Expand Up @@ -721,7 +729,7 @@ def _load_builtin(self, subdir, module_name, third_party_only=False):
def _load_typeshed_builtin(self, subdir, module_name):
"""Load a pyi from typeshed."""
loaded = typeshed.parse_type_definition(
subdir, module_name, self.python_version)
subdir, module_name, self.python_version, self.gen_stub_imports)
if loaded:
filename, mod_ast = loaded
return self.load_file(filename=self.PREFIX + filename,
Expand Down
3 changes: 2 additions & 1 deletion pytype/matcher.py
Expand Up @@ -292,7 +292,8 @@ def _match_value_against_type(self, value, other_type, subst, view):

if isinstance(left, abstract.TypeParameterInstance) and (
isinstance(left.instance, (abstract.CallableClass,
function.Signature))):
function.Signature)) or
left.instance is abstract_utils.DUMMY_CONTAINER):
if isinstance(other_type, abstract.TypeParameter):
new_subst = self._match_type_param_against_type_param(
left.param, other_type, subst, view)
Expand Down
12 changes: 9 additions & 3 deletions pytype/output.py
Expand Up @@ -218,7 +218,8 @@ def value_to_pytd_type(self, node, v, seen, view):
if isinstance(v, (abstract.Empty, typing_overlay.NoReturn)):
return pytd.NothingType()
elif isinstance(v, abstract.TypeParameterInstance):
if v.module in self._scopes:
if (v.module in self._scopes or
v.instance is abstract_utils.DUMMY_CONTAINER):
return self._typeparam_to_def(node, v.param, v.param.name)
elif v.instance.get_instance_type_parameter(v.full_name).bindings:
# The type parameter was initialized. Set the view to None, since we
Expand Down Expand Up @@ -271,7 +272,10 @@ def value_to_pytd_type(self, node, v, seen, view):
return pytd.GenericType(base_type=pytd.NamedType("builtins.type"),
parameters=(param,))
elif isinstance(v, abstract.Module):
return pytd.NamedType("builtins.module")
if self.ctx.options.gen_stub_imports:
return pytd.Alias(v.name, pytd.Module(v.name, module_name=v.full_name))
else:
return pytd.NamedType("builtins.module")
elif (self._output_mode >= Converter.OutputMode.LITERAL and
isinstance(v, abstract.ConcreteValue) and
isinstance(v.pyval, (int, str, bytes))):
Expand Down Expand Up @@ -354,7 +358,9 @@ def value_to_pytd_def(self, node, v, name):
Returns:
A PyTD definition.
"""
if isinstance(v, abstract.BoundFunction):
if self.ctx.options.gen_stub_imports and isinstance(v, abstract.Module):
return pytd.Alias(name, pytd.Module(name, module_name=v.full_name))
elif isinstance(v, abstract.BoundFunction):
d = self.value_to_pytd_def(node, v.underlying, name)
assert isinstance(d, pytd.Function)
sigs = tuple(sig.Replace(params=sig.params[1:]) for sig in d.signatures)
Expand Down
12 changes: 8 additions & 4 deletions pytype/overlays/collections_overlay.py
Expand Up @@ -22,7 +22,8 @@ def namedtuple_ast(name,
fields,
defaults,
python_version=None,
strict_namedtuple_checks=True):
strict_namedtuple_checks=True,
gen_stub_imports=True):
"""Make an AST with a namedtuple definition for the given name and fields.

Args:
Expand All @@ -33,7 +34,8 @@ def namedtuple_ast(name,
strict_namedtuple_checks: Whether to enable a stricter type annotation
hierarchy for generated NamedType. e.g. Tuple[n*[Any]] instead of tuple.
This should usually be set to the value of
ctx.options.strict_namedtuple_checks
ctx.options.strict_namedtuple_checks.
gen_stub_imports: Set this to the value of ctx.options.gen_stub_imports.

Returns:
A pytd.TypeDeclUnit with the namedtuple definition in its classes.
Expand Down Expand Up @@ -79,7 +81,8 @@ def _replace(self: {typevar}, **kwds) -> {typevar}: ...
repeat_any=_repeat_type("typing.Any", num_fields),
fields_as_parameters=fields_as_parameters,
field_names_as_strings=field_names_as_strings)
return parser.parse_string(nt, python_version=python_version)
return parser.parse_string(nt, python_version=python_version,
gen_stub_imports=gen_stub_imports)


class CollectionsOverlay(overlay.Overlay):
Expand Down Expand Up @@ -303,7 +306,8 @@ class have to be changed to match the number and names of the fields, we
field_names,
defaults,
python_version=self.ctx.python_version,
strict_namedtuple_checks=self.ctx.options.strict_namedtuple_checks)
strict_namedtuple_checks=self.ctx.options.strict_namedtuple_checks,
gen_stub_imports=self.ctx.options.gen_stub_imports)
mapping = self._get_known_types_mapping()

# A truly well-formed pyi for the namedtuple will have references to the new
Expand Down