Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: python/mypy
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.11.1
Choose a base ref
...
head repository: python/mypy
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.11.2
Choose a head ref
  • 6 commits
  • 17 files changed
  • 2 contributors

Commits on Aug 24, 2024

  1. Bump version to 1.11.2+dev

    hauntsaninja committed Aug 24, 2024

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    14ab742 View commit details
  2. Revert "Fix RawExpressionType.accept crash with `--cache-fine-grain…

    …ed`" (#17637)
    
    Reverts #17588
    
    (cherry picked from commit 41dcf1a)
    ilevkivskyi authored and hauntsaninja committed Aug 24, 2024
    Copy the full SHA
    778542b View commit details
  3. Revert "Fix Literal strings containing pipe characters" (#17638)

    Reverts #17148
    
    (cherry picked from commit bc39f17)
    ilevkivskyi authored and hauntsaninja committed Aug 24, 2024
    Copy the full SHA
    32675dd View commit details
  4. Unwrap TypedDict item types before storing (#17640)

    Fixes #17604
    Fixes #17608
    
    Fix is trivial, rectify an obvious omission in my original PR.
    
    (cherry picked from commit b56f357)
    ilevkivskyi authored and hauntsaninja committed Aug 24, 2024
    Copy the full SHA
    7d805b3 View commit details
  5. An alternative fix for a union-like literal string (#17639)

    It is unfortunate to add two extra slots to a common type (and I guess
    this is why it was rejected in the original PR), but all other
    alternatives I tried are hacky and/or dangerous. So, this is a price to
    pay for introducing a new type syntax.
    
    ---------
    
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    (cherry picked from commit 3da16bd)
    ilevkivskyi authored and hauntsaninja committed Aug 24, 2024
    Copy the full SHA
    917cc75 View commit details
  6. Bump version to 1.11.2

    hauntsaninja committed Aug 24, 2024
    Copy the full SHA
    789f02c View commit details
9 changes: 6 additions & 3 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
@@ -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.
@@ -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

31 changes: 13 additions & 18 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
@@ -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:
@@ -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)
@@ -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)
4 changes: 3 additions & 1 deletion mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 1 addition & 2 deletions mypy/server/astmerge.py
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 4 additions & 12 deletions mypy/stubutil.py
Original file line number Diff line number Diff line change
@@ -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 = ()
@@ -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)
4 changes: 0 additions & 4 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
@@ -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:
@@ -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:
25 changes: 17 additions & 8 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
@@ -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:
@@ -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:
@@ -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])
@@ -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"):
@@ -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)
@@ -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):
@@ -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
Loading