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

1.11.2 backport bugfixes #17677

Merged
merged 4 commits into from
Aug 24, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,12 @@ def parse_type_string(
"""
try:
_, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None)
return RawExpressionType(expr_string, expr_fallback_name, line, column, node=node)
if isinstance(node, (UnboundType, UnionType)) and node.original_str_expr is None:
node.original_str_expr = expr_string
node.original_str_fallback = expr_fallback_name
return node
else:
return RawExpressionType(expr_string, expr_fallback_name, line, column)
except (SyntaxError, ValueError):
# Note: the parser will raise a `ValueError` instead of a SyntaxError if
# the string happens to contain things like \x00.
Expand Down Expand Up @@ -1046,8 +1051,6 @@ def set_type_optional(self, type: Type | None, initializer: Expression | None) -
return
# Indicate that type should be wrapped in an Optional if arg is initialized to None.
optional = isinstance(initializer, NameExpr) and initializer.name == "None"
if isinstance(type, RawExpressionType) and type.node is not None:
type = type.node
if isinstance(type, UnboundType):
type.optional = optional

Expand Down
31 changes: 13 additions & 18 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3437,10 +3437,10 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
def analyze_lvalues(self, s: AssignmentStmt) -> None:
# We cannot use s.type, because analyze_simple_literal_type() will set it.
explicit = s.unanalyzed_type is not None
final_type = self.unwrap_final_type(s.unanalyzed_type)
if final_type is not None:
if self.is_final_type(s.unanalyzed_type):
# We need to exclude bare Final.
if not final_type.args:
assert isinstance(s.unanalyzed_type, UnboundType)
if not s.unanalyzed_type.args:
explicit = False

if s.rvalue:
Expand Down Expand Up @@ -3506,19 +3506,19 @@ def unwrap_final(self, s: AssignmentStmt) -> bool:

Returns True if Final[...] was present.
"""
final_type = self.unwrap_final_type(s.unanalyzed_type)
if final_type is None:
if not s.unanalyzed_type or not self.is_final_type(s.unanalyzed_type):
return False
if len(final_type.args) > 1:
self.fail("Final[...] takes at most one type argument", final_type)
assert isinstance(s.unanalyzed_type, UnboundType)
if len(s.unanalyzed_type.args) > 1:
self.fail("Final[...] takes at most one type argument", s.unanalyzed_type)
invalid_bare_final = False
if not final_type.args:
if not s.unanalyzed_type.args:
s.type = None
if isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs:
invalid_bare_final = True
self.fail("Type in Final[...] can only be omitted if there is an initializer", s)
else:
s.type = final_type.args[0]
s.type = s.unanalyzed_type.args[0]

if s.type is not None and self.is_classvar(s.type):
self.fail("Variable should not be annotated with both ClassVar and Final", s)
Expand Down Expand Up @@ -4937,18 +4937,13 @@ def is_classvar(self, typ: Type) -> bool:
return False
return sym.node.fullname == "typing.ClassVar"

def unwrap_final_type(self, typ: Type | None) -> UnboundType | None:
if typ is None:
return None
typ = typ.resolve_string_annotation()
def is_final_type(self, typ: Type | None) -> bool:
if not isinstance(typ, UnboundType):
return None
return False
sym = self.lookup_qualified(typ.name, typ)
if not sym or not sym.node:
return None
if sym.node.fullname in FINAL_TYPE_NAMES:
return typ
return None
return False
return sym.node.fullname in FINAL_TYPE_NAMES

def fail_invalid_classvar(self, context: Context) -> None:
self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context)
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,9 @@ def analyze_typeddict_classdef_fields(
return None, [], [], set() # Need to defer
types.append(analyzed)
if not has_placeholder(analyzed):
stmt.type = analyzed
stmt.type = (
analyzed.item if isinstance(analyzed, RequiredType) else analyzed
)
# ...despite possible minor failures that allow further analysis.
if stmt.type is None or hasattr(stmt, "new_syntax") and not stmt.new_syntax:
self.fail(TPDICT_CLASS_ERROR, stmt)
Expand Down
3 changes: 1 addition & 2 deletions mypy/server/astmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,7 @@ def visit_typeddict_type(self, typ: TypedDictType) -> None:
typ.fallback.accept(self)

def visit_raw_expression_type(self, t: RawExpressionType) -> None:
if t.node is not None:
t.node.accept(self)
pass

def visit_literal_type(self, typ: LiteralType) -> None:
typ.fallback.accept(self)
Expand Down
16 changes: 4 additions & 12 deletions mypy/stubutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,7 @@
from mypy.modulefinder import ModuleNotFoundReason
from mypy.moduleinspect import InspectError, ModuleInspect
from mypy.stubdoc import ArgSig, FunctionSig
from mypy.types import (
AnyType,
NoneType,
RawExpressionType,
Type,
TypeList,
TypeStrVisitor,
UnboundType,
UnionType,
)
from mypy.types import AnyType, NoneType, Type, TypeList, TypeStrVisitor, UnboundType, UnionType

# Modules that may fail when imported, or that may have side effects (fully qualified).
NOT_IMPORTABLE_MODULES = ()
Expand Down Expand Up @@ -302,11 +293,12 @@ def args_str(self, args: Iterable[Type]) -> str:
The main difference from list_str is the preservation of quotes for string
arguments
"""
types = ["builtins.bytes", "builtins.str"]
res = []
for arg in args:
arg_str = arg.accept(self)
if isinstance(arg, RawExpressionType):
res.append(repr(arg.literal_value))
if isinstance(arg, UnboundType) and arg.original_str_fallback in types:
res.append(f"'{arg_str}'")
else:
res.append(arg_str)
return ", ".join(res)
Expand Down
4 changes: 0 additions & 4 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,6 @@ def visit_typeddict_type(self, t: TypedDictType) -> T:
return self.query_types(t.items.values())

def visit_raw_expression_type(self, t: RawExpressionType) -> T:
if t.node is not None:
return t.node.accept(self)
return self.strategy([])

def visit_literal_type(self, t: LiteralType) -> T:
Expand Down Expand Up @@ -524,8 +522,6 @@ def visit_typeddict_type(self, t: TypedDictType) -> bool:
return self.query_types(list(t.items.values()))

def visit_raw_expression_type(self, t: RawExpressionType) -> bool:
if t.node is not None:
return t.node.accept(self)
return self.default

def visit_literal_type(self, t: LiteralType) -> bool:
Expand Down
25 changes: 17 additions & 8 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,6 @@ def visit_callable_type(
return ret

def anal_type_guard(self, t: Type) -> Type | None:
t = t.resolve_string_annotation()
if isinstance(t, UnboundType):
sym = self.lookup_qualified(t.name, t)
if sym is not None and sym.node is not None:
Expand All @@ -1126,7 +1125,6 @@ def anal_type_guard_arg(self, t: UnboundType, fullname: str) -> Type | None:
return None

def anal_type_is(self, t: Type) -> Type | None:
t = t.resolve_string_annotation()
if isinstance(t, UnboundType):
sym = self.lookup_qualified(t.name, t)
if sym is not None and sym.node is not None:
Expand All @@ -1144,7 +1142,6 @@ def anal_type_is_arg(self, t: UnboundType, fullname: str) -> Type | None:

def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type:
"""Analyze signature argument type for *args and **kwargs argument."""
t = t.resolve_string_annotation()
if isinstance(t, UnboundType) and t.name and "." in t.name and not t.args:
components = t.name.split(".")
tvar_name = ".".join(components[:-1])
Expand Down Expand Up @@ -1235,8 +1232,6 @@ def visit_raw_expression_type(self, t: RawExpressionType) -> Type:
# make signatures like "foo(x: 20) -> None" legal, we can change
# this method so it generates and returns an actual LiteralType
# instead.
if t.node is not None:
return t.node.accept(self)

if self.report_invalid_types:
if t.base_type_name in ("builtins.int", "builtins.bool"):
Expand Down Expand Up @@ -1499,7 +1494,6 @@ def analyze_callable_args(
invalid_unpacks: list[Type] = []
second_unpack_last = False
for i, arg in enumerate(arglist.items):
arg = arg.resolve_string_annotation()
if isinstance(arg, CallableArgument):
args.append(arg.typ)
names.append(arg.name)
Expand Down Expand Up @@ -1580,6 +1574,22 @@ def analyze_literal_type(self, t: UnboundType) -> Type:
return UnionType.make_union(output, line=t.line)

def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None:
# This UnboundType was originally defined as a string.
if (
isinstance(arg, ProperType)
and isinstance(arg, (UnboundType, UnionType))
and arg.original_str_expr is not None
):
assert arg.original_str_fallback is not None
return [
LiteralType(
value=arg.original_str_expr,
fallback=self.named_type(arg.original_str_fallback),
line=arg.line,
column=arg.column,
)
]

# If arg is an UnboundType that was *not* originally defined as
# a string, try expanding it in case it's a type alias or something.
if isinstance(arg, UnboundType):
Expand Down Expand Up @@ -2564,8 +2574,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> None:
self.process_types(list(t.items.values()))

def visit_raw_expression_type(self, t: RawExpressionType) -> None:
if t.node is not None:
t.node.accept(self)
pass

def visit_literal_type(self, t: LiteralType) -> None:
pass
Expand Down
Loading
Loading