Skip to content

stubtest: rewrite #8325

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 74 commits into from
Feb 6, 2020
Merged
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
2577636
stubtest: don't hardcode python version
Jan 15, 2020
2d04385
stubtest: fix build
Jan 15, 2020
908ef64
stubtest: recognise typealias
Jan 15, 2020
33b98cd
stubtest: use argparse, support custom typeshed dir
Jan 16, 2020
3161d43
stubtest: [minor] blacken
Jan 16, 2020
03d223d
stubtest: [minor] import nits
Jan 16, 2020
8036364
stubtest: [minor] renames, reorder parameters
Jan 16, 2020
e861ff9
stubtest: [wip] start to use runtime objects directly
Jan 16, 2020
3651e6f
stubtest: [minor] make parameter names for verify_* consistent
Jan 16, 2020
ad2a7c8
stubtest: gut error handling
Jan 16, 2020
8deb02a
stubtest: add support for missing things
Jan 16, 2020
91391bb
stubtest: implement verify module
Jan 16, 2020
092961f
stubtest: add trace for easier debugging
Jan 16, 2020
6eae4db
stubtest: implement verify class
Jan 16, 2020
fd9b1d1
stubtest: implement verify missing
Jan 16, 2020
c9ef96a
stubtest: implement verify function
Jan 16, 2020
fe75485
stubtest: implement verify var
Jan 16, 2020
53f768e
stubtest: logging improvements
Jan 16, 2020
ac63aee
stubtest: improve verify function
Jan 16, 2020
eedb3f2
stubtest: implement verify overload
Jan 16, 2020
be5c715
stubtest: more improvements to logging
Jan 16, 2020
d43cefb
stubtest: add --ignore-missing-stub option
Jan 16, 2020
be72175
stubtest: [minor] make order more deterministic
Jan 16, 2020
7876ca2
stubtest: [minor] descend through stubs less hackily
Jan 16, 2020
8a41b6d
stubtest: [minor] clean up imports
Jan 17, 2020
f761e8a
stubtest: [minor] remove debugging decorator
Jan 17, 2020
7b5ecfe
stubtest: small improvements for functions
Jan 17, 2020
3e629ae
stubtest: add --concise option
Jan 17, 2020
013d04e
stubtest: add exit code
Jan 17, 2020
0c7a57d
stubtest: redo verify function
Jan 18, 2020
aac7a57
stubtest: add ability to whitelist errors
Jan 18, 2020
9a68062
stubtest: [minor] clean up error handling
Jan 19, 2020
871be3e
stubtest: add --check-typeshed option
Jan 19, 2020
ae96f92
stubtest: [minor] handle distutils.command a little better
Jan 19, 2020
412fcf3
stubtest: adjust module level things we check in stubs
Jan 19, 2020
80b3d26
stubtest: check for mistaken positional only args
Jan 19, 2020
59a38b9
stubtest: be more permissive about positional-only arg names
Jan 19, 2020
b0f226c
stubtest: [minor] make error order more deterministic
Jan 20, 2020
b1139ea
stubtest: only mypy build once
Jan 20, 2020
141e800
stubtest: [minor] remove antigravity from --check-typeshed
Jan 20, 2020
f4b1a2a
stubtest: make verify_var work
Jan 20, 2020
22c4cf2
stubtest: verify types of argument default values
Jan 21, 2020
8481380
stubtest: pretend Literal[0, 1] is subtype of bool
Jan 21, 2020
2568583
stubtest: output unused whitelist entries
Jan 21, 2020
2de4fac
stubtest: [minor] deduplicate, sort --output-whitelist, fix exit code
Jan 21, 2020
b778b44
stubtest: add more documentation
Jan 21, 2020
8bebf33
stubtest: [minor] rename --output-whitelist to --generate-whitelist
Jan 21, 2020
49dab43
stubtest: [minor] suppress warnings
Jan 21, 2020
23bd82d
stubtest: look into the mro for attributes
Jan 22, 2020
1b5a18e
stubtest: better support @property and other decorators
Jan 22, 2020
30f5f4a
stubtest: check classmethod and staticmethod
Jan 22, 2020
31a299c
stubtest: [minor] support comments in whitelist
Jan 22, 2020
8a37fda
stubtest: [refactor] split up verify_funcitem
Jan 22, 2020
da90df7
stubtest: [minor] suggest positional-only name
Jan 22, 2020
4c05bca
stubtest: add __str__ for Signature
Jan 23, 2020
3afda91
stubtest: implement smarter overload checking
Jan 23, 2020
bb82c18
stubtest: improve typeinfo output, simplify descriptions
Jan 23, 2020
9ea10bd
stubtest: [minor] blacken
Jan 24, 2020
8686c33
stubtest: improve decorator handling, fix classmethod signature
Jan 26, 2020
f37e588
stubtest: fix classmethod and staticmethod introspection
Jan 26, 2020
0dbd899
stubtest: [minor] factor out is_dunder, check suffix
Jan 26, 2020
204c168
stubtest: fix proper_plugin, other selfcheck errors
Jan 26, 2020
523761c
stubtest: find submodules when explicitly testing a module
Jan 28, 2020
729181a
stubtest: remove f-strings for py35
Jan 28, 2020
36163f7
stubtest: remove variable annotations for py35
Jan 28, 2020
63cfe67
stubtest: remove trailing commas for py35
Jan 28, 2020
c5b99c6
stubtest: other changes for py35
Jan 28, 2020
b757b22
stubtest: [minor] use line length 99 to match project
Jan 28, 2020
e3f4cd3
stubtest: add a flag to ignore positional-only errors
Jan 29, 2020
6efc27f
stubtest: check typevar upper bounds for default values
Jan 30, 2020
dd07f78
stubtest: don't crash because of bpo-39504
Jan 31, 2020
13627d9
stubtest: avoid false positive when defining enums
Feb 1, 2020
6f2cb39
stubtest: allow multiple whitelists
Feb 1, 2020
0b89ff9
stubtest: [minor] improve help message
Feb 1, 2020
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
Prev Previous commit
Next Next commit
stubtest: redo verify function
Rework things to avoid false positives / order nitpicks
Make checks involving *args, **kwargs a little more sophisticated
  • Loading branch information
hauntsaninja committed Jan 23, 2020
commit 0c7a57da662cc93c3fc25647748ea8c0df79d21c
227 changes: 157 additions & 70 deletions scripts/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,11 @@ def verify_funcitem(
if isinstance(runtime, Missing):
yield Error(object_path, "is not present at runtime", stub, runtime)
return
if not isinstance(
runtime, (types.FunctionType, types.BuiltinFunctionType)
) and not inspect.ismethoddescriptor(runtime):
if (
not isinstance(runtime, (types.FunctionType, types.BuiltinFunctionType))
and not isinstance(runtime, (types.MethodType, types.BuiltinMethodType))
and not inspect.ismethoddescriptor(runtime)
):
yield Error(object_path, "is not a function", stub, runtime)
return

Expand All @@ -218,79 +220,162 @@ def verify_funcitem(
def runtime_printer(s: Any) -> str:
return "def " + str(inspect.signature(s))

i, j = 0, 0
stub_args = stub.arguments
runtime_args = list(signature.parameters.values())
while i < len(stub_args) or j < len(runtime_args):
if i >= len(stub_args):
# Ignore the error if the stub doesn't take **kwargs, for cases where the stub
# just listed out the keyword parameters the function takes
if runtime_args[j].kind != inspect.Parameter.VAR_KEYWORD:
yield Error(
object_path,
f'is inconsistent, stub does not have argument "{runtime_args[j].name}"',
stub,
runtime,
runtime_printer=runtime_printer,
)
j += 1
continue
if j >= len(runtime_args):
yield Error(
object_path,
f"is inconsistent, runtime does not have argument {stub_args[i].variable.name}",
stub,
runtime,
runtime_printer=runtime_printer,
)
i += 1
continue

# TODO: maybe don't check by name for positional-only args
# TODO: stricter checking of positional-only, keyword-only
# TODO: check type compatibility of default args
# TODO: overloads are sometimes pretty deceitful, so handle that better

# Allow *args and **kwargs to soak up extra args and kwargs
stub_arg, runtime_arg = stub_args[i], runtime_args[j]
if (stub_arg.kind == mypy.nodes.ARG_STAR) and (
runtime_arg.kind != inspect.Parameter.VAR_POSITIONAL
):
j += 1
continue
if (stub_arg.kind != mypy.nodes.ARG_STAR) and (
runtime_arg.kind == inspect.Parameter.VAR_POSITIONAL
):
i += 1
continue
def make_error(message: str) -> Error:
return Error(
object_path,
"is inconsistent, " + message,
stub,
runtime,
runtime_printer=runtime_printer,
)

if (stub_arg.kind == mypy.nodes.ARG_STAR2) and (
runtime_arg.kind != inspect.Parameter.VAR_KEYWORD
):
j += 1
continue
if (stub_arg.kind != mypy.nodes.ARG_STAR2) and (
runtime_arg.kind == inspect.Parameter.VAR_KEYWORD
stub_args_pos = []
stub_args_kwonly = {}
stub_args_varpos = None
stub_args_varkw = None

for stub_arg in stub.arguments:
if stub_arg.kind in (nodes.ARG_POS, nodes.ARG_OPT):
stub_args_pos.append(stub_arg)
elif stub_arg.kind in (nodes.ARG_NAMED, nodes.ARG_NAMED_OPT):
stub_args_kwonly[stub_arg.variable.name] = stub_arg
elif stub_arg.kind == nodes.ARG_STAR:
stub_args_varpos = stub_arg
elif stub_arg.kind == nodes.ARG_STAR2:
stub_args_varkw = stub_arg
else:
raise ValueError

runtime_args_pos = []
runtime_args_kwonly = {}
runtime_args_varpos = None
runtime_args_varkw = None

for runtime_arg in signature.parameters.values():
if runtime_arg.kind in (
inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
):
i += 1
continue

runtime_args_pos.append(runtime_arg)
elif runtime_arg.kind == inspect.Parameter.KEYWORD_ONLY:
runtime_args_kwonly[runtime_arg.name] = runtime_arg
elif runtime_arg.kind == inspect.Parameter.VAR_POSITIONAL:
runtime_args_varpos = runtime_arg
elif runtime_arg.kind == inspect.Parameter.VAR_KEYWORD:
runtime_args_varkw = runtime_arg
else:
raise ValueError

def verify_arg_name(
stub_arg: nodes.Argument, runtime_arg: inspect.Parameter
) -> Iterator[Error]:
# Ignore exact names for all dunder methods other than __init__
is_dunder_method = stub.name != "__init__" and stub.name.startswith("__")
if stub.name != "__init__" and stub.name.startswith("__"):
return
if stub_arg.variable.name.replace("_", "") != runtime_arg.name.replace("_", ""):
yield make_error(
f'stub argument "{stub_arg.variable.name}" differs from '
f'runtime argument "{runtime_arg.name}"'
)

def verify_arg_default_value(
stub_arg: nodes.Argument, runtime_arg: inspect.Parameter
) -> Iterator[Error]:
if runtime_arg.default != inspect.Parameter.empty:
# TODO: Check that the default value is compatible with the stub type
if stub_arg.kind not in (nodes.ARG_OPT, nodes.ARG_NAMED_OPT):
yield make_error(
f'runtime argument "{runtime_arg.name}" has a default value '
"but stub argument does not"
)
else:
if stub_arg.kind in (nodes.ARG_OPT, nodes.ARG_NAMED_OPT):
yield make_error(
f'stub argument "{stub_arg.variable.name}" has a default '
"value but runtime argument does not"
)

# Check positional arguments match up
for stub_arg, runtime_arg in zip(stub_args_pos, runtime_args_pos):
yield from verify_arg_name(stub_arg, runtime_arg)
yield from verify_arg_default_value(stub_arg, runtime_arg)
if (
stub_arg.variable.name.replace("_", "") != runtime_arg.name.replace("_", "")
and not is_dunder_method
runtime_arg.kind == inspect.Parameter.POSITIONAL_ONLY
and not stub_arg.variable.name.startswith("__")
and not stub_arg.variable.name.strip("_") == "self"
and not stub.name.startswith("__") # noisy for dunder methods
):
yield make_error(
f'stub argument "{stub_arg.variable.name}" should be '
"positional-only (rename with a leading double underscore)"
)

# Checks involving *args
if len(stub_args_pos) == len(runtime_args_pos):
if stub_args_varpos is None and runtime_args_varpos is not None:
yield make_error(
f'stub does not have *args argument "{runtime_args_varpos.name}"'
)
if stub_args_varpos is not None and runtime_args_varpos is None:
yield make_error(
f'runtime does not have *args argument "{stub_args_varpos.variable.name}"'
)
elif len(stub_args_pos) > len(runtime_args_pos):
if runtime_args_varpos is None:
for stub_arg in stub_args_pos[len(runtime_args_pos) :]:
# If the variable is in runtime_args_kwonly, it's just mislabelled as not a
# keyword-only argument; we report the error while checking keyword-only arguments
if stub_arg.variable.name not in runtime_args_kwonly:
yield make_error(
f'runtime does not have argument "{stub_arg.variable.name}"'
)
# We do not check whether stub takes *args when the runtime does, for cases where the stub
# just listed out the extra parameters the function takes
elif len(stub_args_pos) < len(runtime_args_pos):
if stub_args_varpos is None:
for runtime_arg in runtime_args_pos[len(stub_args_pos) :]:
yield make_error(f'stub does not have argument "{runtime_arg.name}"')
elif runtime_args_pos is None:
yield make_error(
f'runtime does not have *args argument "{stub_args_varpos.variable.name}"'
)

# Check keyword-only args
for arg in set(stub_args_kwonly) & set(runtime_args_kwonly):
stub_arg, runtime_arg = stub_args_kwonly[arg], runtime_args_kwonly[arg]
yield from verify_arg_name(stub_arg, runtime_arg)
yield from verify_arg_default_value(stub_arg, runtime_arg)

# Checks involving **kwargs
if stub_args_varkw is None and runtime_args_varkw is not None:
# We do not check whether stub takes **kwargs when the runtime does, for cases where the
# stub just listed out the extra keyword parameters the function takes
# Also check against positional parameters, to avoid a nitpicky message when an argument
# isn't marked as keyword-only
stub_pos_names = set(stub_arg.variable.name for stub_arg in stub_args_pos)
if not set(runtime_args_kwonly).issubset(
set(stub_args_kwonly) | stub_pos_names
):
yield Error(
object_path,
f'is inconsistent, stub argument "{stub_arg.variable.name}" differs from '
f'runtime argument "{runtime_arg.name}"',
stub,
runtime,
runtime_printer=runtime_printer,
yield make_error(
f'stub does not have **kwargs argument "{runtime_args_varkw.name}"'
)
i += 1
j += 1
if stub_args_varkw is not None and runtime_args_varkw is None:
yield make_error(
f'runtime does not have **kwargs argument "{stub_args_varkw.variable.name}"'
)
if runtime_args_varkw is None or not set(runtime_args_kwonly).issubset(
set(stub_args_kwonly)
):
for arg in set(stub_args_kwonly) - set(runtime_args_kwonly):
yield make_error(f'runtime does not have argument "{arg}"')
if stub_args_varkw is None or not set(stub_args_kwonly).issubset(
set(runtime_args_kwonly)
):
for arg in set(runtime_args_kwonly) - set(stub_args_kwonly):
if arg in set(stub_arg.variable.name for stub_arg in stub_args_pos):
yield make_error(f'stub argument "{arg}" is not keyword-only')
else:
yield make_error(f'stub does not have argument "{arg}"')


@verify.register(Missing)
Expand Down Expand Up @@ -321,6 +406,8 @@ def verify_var(
def verify_overloadedfuncdef(
stub: nodes.OverloadedFuncDef, runtime: MaybeMissing[Any], object_path: List[str]
) -> Iterator[Error]:
# TODO: Overloads can be pretty deceitful, so maybe be more permissive when dealing with them
# For a motivating example, look at RawConfigParser.items and RawConfigParser.get
for func in stub.items:
yield from verify(func, runtime, object_path)

Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy