-
-
Notifications
You must be signed in to change notification settings - Fork 3k
New semantic analyzer: Make assignment analysis logic more straightforward #6527
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
Changes from all commits
b6fd5d9
5c1e97c
6a80f22
5f9cfe4
88dec62
e27727f
9a6e3c3
7ba8c8f
746a424
1e1804b
555a209
4378bd7
259eb37
2e6e8e8
b3321dc
91e7118
ca9bf7c
a9b746e
e4f4003
98c8da8
469ad27
4a80d0c
7c25d3d
adc5c8a
9e6ad03
58c88b5
bf330d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,14 @@ | |
|
||
from typing import Tuple, Optional | ||
|
||
from mypy.types import Type, Instance, CallableType, NoneTyp, TupleType, AnyType, TypeOfAny | ||
from mypy.types import ( | ||
Type, Instance, CallableType, NoneTyp, TupleType, AnyType, PlaceholderType, | ||
TypeOfAny | ||
) | ||
from mypy.nodes import ( | ||
AssignmentStmt, NewTypeExpr, CallExpr, NameExpr, RefExpr, Context, StrExpr, BytesExpr, | ||
UnicodeExpr, Block, FuncDef, Argument, TypeInfo, Var, SymbolTableNode, GDEF, MDEF, ARG_POS | ||
UnicodeExpr, Block, FuncDef, Argument, TypeInfo, Var, SymbolTableNode, MDEF, ARG_POS, | ||
PlaceholderNode | ||
) | ||
from mypy.newsemanal.semanal_shared import SemanticAnalyzerInterface | ||
from mypy.options import Options | ||
|
@@ -26,17 +30,36 @@ def __init__(self, | |
self.api = api | ||
self.msg = msg | ||
|
||
def process_newtype_declaration(self, s: AssignmentStmt) -> None: | ||
"""Check if s declares a NewType; if yes, store it in symbol table.""" | ||
# Extract and check all information from newtype declaration | ||
def process_newtype_declaration(self, s: AssignmentStmt) -> bool: | ||
"""Check if s declares a NewType; if yes, store it in symbol table. | ||
|
||
Return True if it's a NewType declaration. The current target may be | ||
deferred as a side effect if the base type is not ready, even if | ||
the return value is True. | ||
|
||
The logic in this function mostly copies the logic for visit_class_def() | ||
with a single (non-Generic) base. | ||
""" | ||
name, call = self.analyze_newtype_declaration(s) | ||
if name is None or call is None: | ||
return | ||
|
||
old_type = self.check_newtype_args(name, call, s) | ||
call.analyzed = NewTypeExpr(name, old_type, line=call.line) | ||
return False | ||
# OK, now we know this is a NewType. But the base type may be not ready yet, | ||
# add placeholder as we do for ClassDef. | ||
|
||
fullname = self.api.qualified_name(name) | ||
if (not call.analyzed or | ||
isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info): | ||
# Start from labeling this as a future class, as we do for normal ClassDefs. | ||
self.api.add_symbol(name, PlaceholderNode(fullname, s, becomes_typeinfo=True), s) | ||
|
||
old_type, should_defer = self.check_newtype_args(name, call, s) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It wasn't immediately clear to me what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately |
||
if not call.analyzed: | ||
call.analyzed = NewTypeExpr(name, old_type, line=call.line) | ||
if old_type is None: | ||
return | ||
if should_defer: | ||
# Base type is not ready. | ||
self.api.defer() | ||
return True | ||
|
||
# Create the corresponding class definition if the aliased type is subtypeable | ||
if isinstance(old_type, TupleType): | ||
|
@@ -48,9 +71,14 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: | |
self.fail("NewType cannot be used with protocol classes", s) | ||
newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type) | ||
else: | ||
message = "Argument 2 to NewType(...) must be subclassable (got {})" | ||
self.fail(message.format(self.msg.format(old_type)), s) | ||
return | ||
if old_type is not None: | ||
message = "Argument 2 to NewType(...) must be subclassable (got {})" | ||
self.fail(message.format(self.msg.format(old_type)), s) | ||
# Otherwise the error was already reported. | ||
old_type = AnyType(TypeOfAny.from_error) | ||
object_type = self.api.named_type('__builtins__.object') | ||
newtype_class_info = self.build_newtype_typeinfo(name, old_type, object_type) | ||
newtype_class_info.fallback_to_any = True | ||
|
||
check_for_explicit_any(old_type, self.options, self.api.is_typeshed_stub_file, self.msg, | ||
context=s) | ||
|
@@ -59,13 +87,16 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: | |
self.msg.unimported_type_becomes_any("Argument 2 to NewType(...)", old_type, s) | ||
|
||
# If so, add it to the symbol table. | ||
node = self.api.lookup(name, s) | ||
if node is None: | ||
self.fail("Could not find {} in current namespace".format(name), s) | ||
return | ||
# TODO: why does NewType work in local scopes despite always being of kind GDEF? | ||
node.kind = GDEF | ||
call.analyzed.info = node.node = newtype_class_info | ||
assert isinstance(call.analyzed, NewTypeExpr) | ||
# As we do for normal classes, create the TypeInfo only once, then just | ||
# update base classes on next iterations (to get rid of placeholders there). | ||
if not call.analyzed.info: | ||
call.analyzed.info = newtype_class_info | ||
else: | ||
call.analyzed.info.bases = newtype_class_info.bases | ||
self.api.add_symbol(name, call.analyzed.info, s) | ||
newtype_class_info.line = s.line | ||
return True | ||
|
||
def analyze_newtype_declaration(self, | ||
s: AssignmentStmt) -> Tuple[Optional[str], Optional[CallExpr]]: | ||
|
@@ -76,13 +107,18 @@ def analyze_newtype_declaration(self, | |
and isinstance(s.rvalue, CallExpr) | ||
and isinstance(s.rvalue.callee, RefExpr) | ||
and s.rvalue.callee.fullname == 'typing.NewType'): | ||
lvalue = s.lvalues[0] | ||
name = s.lvalues[0].name | ||
if not lvalue.is_inferred_def: | ||
if s.type: | ||
self.fail("Cannot declare the type of a NewType declaration", s) | ||
else: | ||
self.fail("Cannot redefine '%s' as a NewType" % name, s) | ||
|
||
if s.type: | ||
self.fail("Cannot declare the type of a NewType declaration", s) | ||
|
||
names = self.api.current_symbol_table() | ||
existing = names.get(name) | ||
# Give a better error message than generic "Name already defined", | ||
# like the old semantic analyzer does. | ||
if (existing and | ||
not isinstance(existing.node, PlaceholderNode) and not s.rvalue.analyzed): | ||
self.fail("Cannot redefine '%s' as a NewType" % name, s) | ||
|
||
# This dummy NewTypeExpr marks the call as sufficiently analyzed; it will be | ||
# overwritten later with a fully complete NewTypeExpr if there are no other | ||
|
@@ -91,12 +127,17 @@ def analyze_newtype_declaration(self, | |
|
||
return name, call | ||
|
||
def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Optional[Type]: | ||
def check_newtype_args(self, name: str, call: CallExpr, | ||
context: Context) -> Tuple[Optional[Type], bool]: | ||
"""Ananlyze base type in NewType call. | ||
|
||
Return a tuple (type, should defer). | ||
""" | ||
has_failed = False | ||
args, arg_kinds = call.args, call.arg_kinds | ||
if len(args) != 2 or arg_kinds[0] != ARG_POS or arg_kinds[1] != ARG_POS: | ||
self.fail("NewType(...) expects exactly two positional arguments", context) | ||
return None | ||
return None, False | ||
|
||
# Check first argument | ||
if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): | ||
|
@@ -113,19 +154,22 @@ def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Opt | |
unanalyzed_type = expr_to_unanalyzed_type(args[1]) | ||
except TypeTranslationError: | ||
self.fail(msg, context) | ||
return None | ||
return None, False | ||
|
||
# We want to use our custom error message (see above), so we suppress | ||
# the default error message for invalid types here. | ||
old_type = self.api.anal_type(unanalyzed_type, report_invalid_types=False) | ||
should_defer = False | ||
if old_type is None or isinstance(old_type, PlaceholderType): | ||
should_defer = True | ||
|
||
# The caller of this function assumes that if we return a Type, it's always | ||
# a valid one. So, we translate AnyTypes created from errors into None. | ||
if isinstance(old_type, AnyType) and old_type.is_from_error: | ||
self.fail(msg, context) | ||
return None | ||
return None, False | ||
|
||
return None if has_failed else old_type | ||
return None if has_failed else old_type, should_defer | ||
|
||
def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance) -> TypeInfo: | ||
info = self.api.basic_new_typeinfo(name, base_type) | ||
|
Uh oh!
There was an error while loading. Please reload this page.