Skip to content

Commit

Permalink
Make messages use builtin types instead of typing's types (#15070)
Browse files Browse the repository at this point in the history
Adds a new option , `use_lowercase_builtins()`, to choose whether to
report builtin types (`tuple`, `list`, `set`, `frozenset`) as the
builtins instead of the uppercased versions in `typing`. This option is
only set to `True` for python versions that are at least 3.9.

Pipes options all the way through to the messages module. This work
revealed a few places that types are printed with `str()` instead of
using `format_type`, which has been changed. This does change reporting
overall, though this should make messages more consistent.

Adds a hidden flag `--force-uppercase-builtins` that sets the option to
`True` regardless of python version. This allows us to have tests that
are independent of version.
  • Loading branch information
koogoro authored Apr 19, 2023
1 parent 3d9661c commit 768ba66
Show file tree
Hide file tree
Showing 44 changed files with 489 additions and 261 deletions.
12 changes: 1 addition & 11 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,17 +235,7 @@ def _build(

source_set = BuildSourceSet(sources)
cached_read = fscache.read
errors = Errors(
options.show_error_context,
options.show_column_numbers,
options.hide_error_codes,
options.pretty,
options.show_error_end,
lambda path: read_py_file(path, cached_read),
options.show_absolute_path,
options.many_errors_threshold,
options,
)
errors = Errors(options, read_source=lambda path: read_py_file(path, cached_read))
plugin, snapshot = load_plugins(options, errors, stdout, extra_plugins)

# Add catch-all .gitignore to cache dir if we created it
Expand Down
51 changes: 36 additions & 15 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def __init__(
self.expr_checker = mypy.checkexpr.ExpressionChecker(
self, self.msg, self.plugin, per_line_checking_time_ns
)
self.pattern_checker = PatternChecker(self, self.msg, self.plugin)
self.pattern_checker = PatternChecker(self, self.msg, self.plugin, options)

@property
def type_context(self) -> list[Type | None]:
Expand Down Expand Up @@ -483,7 +483,9 @@ def check_first_pass(self) -> None:
"typing.Sequence", [self.named_type("builtins.str")]
)
if not is_subtype(all_.type, seq_str):
str_seq_s, all_s = format_type_distinctly(seq_str, all_.type)
str_seq_s, all_s = format_type_distinctly(
seq_str, all_.type, options=self.options
)
self.fail(
message_registry.ALL_MUST_BE_SEQ_STR.format(str_seq_s, all_s), all_node
)
Expand Down Expand Up @@ -1178,7 +1180,8 @@ def check_func_def(
msg = None
elif typ.arg_names[i] in {"self", "cls"}:
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
erased, ref_type
erased.str_with_options(self.options),
ref_type.str_with_options(self.options),
)
else:
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
Expand Down Expand Up @@ -1323,7 +1326,7 @@ def check_unbound_return_typevar(self, typ: CallableType) -> None:
):
self.note(
"Consider using the upper bound "
f"{format_type(typ.ret_type.upper_bound)} instead",
f"{format_type(typ.ret_type.upper_bound, self.options)} instead",
context=typ.ret_type,
)

Expand Down Expand Up @@ -1430,7 +1433,9 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType, UninhabitedType)
):
self.fail(
message_registry.NON_INSTANCE_NEW_TYPE.format(format_type(bound_type.ret_type)),
message_registry.NON_INSTANCE_NEW_TYPE.format(
format_type(bound_type.ret_type, self.options)
),
fdef,
)
else:
Expand Down Expand Up @@ -2351,7 +2356,10 @@ class Baz(int, Foo, Bar, enum.Flag): ...
enum_base = base
continue
elif enum_base is not None and not base.type.is_enum:
self.fail(f'No non-enum mixin classes are allowed after "{enum_base}"', defn)
self.fail(
f'No non-enum mixin classes are allowed after "{enum_base.str_with_options(self.options)}"',
defn,
)
break

def check_enum_new(self, defn: ClassDef) -> None:
Expand All @@ -2376,7 +2384,7 @@ def has_new_method(info: TypeInfo) -> bool:
if candidate and has_new:
self.fail(
"Only a single data type mixin is allowed for Enum subtypes, "
'found extra "{}"'.format(base),
'found extra "{}"'.format(base.str_with_options(self.options)),
defn,
)
elif candidate:
Expand Down Expand Up @@ -3978,7 +3986,12 @@ def check_member_assignment(

dunder_set = attribute_type.type.get_method("__set__")
if dunder_set is None:
self.fail(message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format(attribute_type), context)
self.fail(
message_registry.DESCRIPTOR_SET_NOT_CALLABLE.format(
attribute_type.str_with_options(self.options)
),
context,
)
return AnyType(TypeOfAny.from_error), get_type, False

bound_method = analyze_decorator_or_funcbase_access(
Expand Down Expand Up @@ -4132,7 +4145,9 @@ def visit_expression_stmt(self, s: ExpressionStmt) -> None:
if error_note_and_code:
error_note, code = error_note_and_code
self.fail(
message_registry.TYPE_MUST_BE_USED.format(format_type(expr_type)), s, code=code
message_registry.TYPE_MUST_BE_USED.format(format_type(expr_type, self.options)),
s,
code=code,
)
self.note(error_note, s, code=code)

Expand Down Expand Up @@ -4962,7 +4977,9 @@ def _make_fake_typeinfo_and_full_name(
# We use the pretty_names_list for error messages but can't
# use it for the real name that goes into the symbol table
# because it can have dots in it.
pretty_names_list = pretty_seq(format_type_distinctly(*base_classes, bare=True), "and")
pretty_names_list = pretty_seq(
format_type_distinctly(*base_classes, options=self.options, bare=True), "and"
)
try:
info, full_name = _make_fake_typeinfo_and_full_name(base_classes, curr_module)
with self.msg.filter_errors() as local_errors:
Expand Down Expand Up @@ -4999,7 +5016,7 @@ def intersect_instance_callable(self, typ: Instance, callable_type: CallableType
gen_name = gen_unique_name(f"<callable subtype of {typ.type.name}>", cur_module.names)

# Synthesize a fake TypeInfo
short_name = format_type_bare(typ)
short_name = format_type_bare(typ, self.options)
cdef, info = self.make_fake_typeinfo(cur_module.fullname, gen_name, short_name, [typ])

# Build up a fake FuncDef so we can populate the symbol table.
Expand Down Expand Up @@ -5205,7 +5222,7 @@ def _check_for_truthy_type(self, t: Type, expr: Expression) -> None:
return

def format_expr_type() -> str:
typ = format_type(t)
typ = format_type(t, self.options)
if isinstance(expr, MemberExpr):
return f'Member "{expr.name}" has type {typ}'
elif isinstance(expr, RefExpr) and expr.fullname:
Expand All @@ -5220,14 +5237,16 @@ def format_expr_type() -> str:
return f"Expression has type {typ}"

if isinstance(t, FunctionLike):
self.fail(message_registry.FUNCTION_ALWAYS_TRUE.format(format_type(t)), expr)
self.fail(
message_registry.FUNCTION_ALWAYS_TRUE.format(format_type(t, self.options)), expr
)
elif isinstance(t, UnionType):
self.fail(message_registry.TYPE_ALWAYS_TRUE_UNIONTYPE.format(format_expr_type()), expr)
elif isinstance(t, Instance) and t.type.fullname == "typing.Iterable":
_, info = self.make_fake_typeinfo("typing", "Collection", "Collection", [])
self.fail(
message_registry.ITERABLE_ALWAYS_TRUE.format(
format_expr_type(), format_type(Instance(info, t.args))
format_expr_type(), format_type(Instance(info, t.args), self.options)
),
expr,
)
Expand Down Expand Up @@ -6012,7 +6031,9 @@ def check_subtype(
note_msg = ""
notes = notes or []
if subtype_label is not None or supertype_label is not None:
subtype_str, supertype_str = format_type_distinctly(orig_subtype, orig_supertype)
subtype_str, supertype_str = format_type_distinctly(
orig_subtype, orig_supertype, options=self.options
)
if subtype_label is not None:
extra_info.append(subtype_label + " " + subtype_str)
if supertype_label is not None:
Expand Down
14 changes: 12 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3967,7 +3967,12 @@ def visit_type_application(self, tapp: TypeApplication) -> Type:
if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias):
# Subscription of a (generic) alias in runtime context, expand the alias.
item = expand_type_alias(
tapp.expr.node, tapp.types, self.chk.fail, tapp.expr.node.no_args, tapp
tapp.expr.node,
tapp.types,
self.chk.fail,
tapp.expr.node.no_args,
tapp,
self.chk.options,
)
item = get_proper_type(item)
if isinstance(item, Instance):
Expand Down Expand Up @@ -4032,7 +4037,12 @@ class LongName(Generic[T]): ...
disallow_any = self.chk.options.disallow_any_generics and self.is_callee
item = get_proper_type(
set_any_tvars(
alias, ctx.line, ctx.column, disallow_any=disallow_any, fail=self.msg.fail
alias,
ctx.line,
ctx.column,
self.chk.options,
disallow_any=disallow_any,
fail=self.msg.fail,
)
)
if isinstance(item, Instance):
Expand Down
10 changes: 8 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:
dunder_get = descriptor_type.type.get_method("__get__")
if dunder_get is None:
mx.msg.fail(
message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), mx.context
message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(
descriptor_type.str_with_options(mx.msg.options)
),
mx.context,
)
return AnyType(TypeOfAny.from_error)

Expand Down Expand Up @@ -694,7 +697,10 @@ def analyze_descriptor_access(descriptor_type: Type, mx: MemberContext) -> Type:

if not isinstance(inferred_dunder_get_type, CallableType):
mx.msg.fail(
message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), mx.context
message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(
descriptor_type.str_with_options(mx.msg.options)
),
mx.context,
)
return AnyType(TypeOfAny.from_error)

Expand Down
24 changes: 19 additions & 5 deletions mypy/checkpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from mypy.meet import narrow_declared_type
from mypy.messages import MessageBuilder
from mypy.nodes import ARG_POS, Context, Expression, NameExpr, TypeAlias, TypeInfo, Var
from mypy.options import Options
from mypy.patterns import (
AsPattern,
ClassPattern,
Expand Down Expand Up @@ -104,7 +105,11 @@ class PatternChecker(PatternVisitor[PatternType]):
# non_sequence_match_type_names
non_sequence_match_types: list[Type]

def __init__(self, chk: mypy.checker.TypeChecker, msg: MessageBuilder, plugin: Plugin) -> None:
options: Options

def __init__(
self, chk: mypy.checker.TypeChecker, msg: MessageBuilder, plugin: Plugin, options: Options
) -> None:
self.chk = chk
self.msg = msg
self.plugin = plugin
Expand All @@ -114,6 +119,7 @@ def __init__(self, chk: mypy.checker.TypeChecker, msg: MessageBuilder, plugin: P
self.non_sequence_match_types = self.generate_types_from_names(
non_sequence_match_type_names
)
self.options = options

def accept(self, o: Pattern, type_context: Type) -> PatternType:
self.type_context.append(type_context)
Expand Down Expand Up @@ -458,8 +464,8 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
elif isinstance(type_info, TypeAlias):
typ = type_info.target
else:
if isinstance(type_info, Var):
name = str(type_info.type)
if isinstance(type_info, Var) and type_info.type is not None:
name = type_info.type.str_with_options(self.options)
else:
name = type_info.name
self.msg.fail(message_registry.CLASS_PATTERN_TYPE_REQUIRED.format(name), o.class_ref)
Expand Down Expand Up @@ -508,7 +514,12 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
)
has_local_errors = local_errors.has_new_errors()
if has_local_errors:
self.msg.fail(message_registry.MISSING_MATCH_ARGS.format(typ), o)
self.msg.fail(
message_registry.MISSING_MATCH_ARGS.format(
typ.str_with_options(self.options)
),
o,
)
return self.early_non_match()

proper_match_args_type = get_proper_type(match_args_type)
Expand Down Expand Up @@ -573,7 +584,10 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
if has_local_errors or key_type is None:
key_type = AnyType(TypeOfAny.from_error)
self.msg.fail(
message_registry.CLASS_PATTERN_UNKNOWN_KEYWORD.format(typ, keyword), pattern
message_registry.CLASS_PATTERN_UNKNOWN_KEYWORD.format(
typ.str_with_options(self.options), keyword
),
pattern,
)

inner_type, inner_rest_type, inner_captures = self.accept(pattern, key_type)
Expand Down
2 changes: 1 addition & 1 deletion mypy/checkstrformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ def apply_field_accessors(
return repl
assert spec.field

temp_errors = Errors()
temp_errors = Errors(self.chk.options)
dummy = DUMMY_FIELD_NAME + spec.field[len(spec.key) :]
temp_ast: Node = parse(
dummy, fnam="<format>", module=None, options=self.chk.options, errors=temp_errors
Expand Down
Loading

0 comments on commit 768ba66

Please sign in to comment.