diff --git a/mypy/build.py b/mypy/build.py index 4713139236d0..b8e70b19b8cb 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -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 diff --git a/mypy/checker.py b/mypy/checker.py index 2b865f645330..d0a89ed78276 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -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]: @@ -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 ) @@ -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 @@ -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, ) @@ -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: @@ -2347,7 +2352,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: @@ -2372,7 +2380,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: @@ -3974,7 +3982,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( @@ -4128,7 +4141,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) @@ -4958,7 +4973,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: @@ -4995,7 +5012,7 @@ def intersect_instance_callable(self, typ: Instance, callable_type: CallableType gen_name = gen_unique_name(f"", 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. @@ -5201,7 +5218,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: @@ -5216,14 +5233,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, ) @@ -6008,7 +6027,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: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c0c4e18d8f1f..0a8dedb1342a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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): @@ -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): diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 2ba917715be0..c2c6b3555805 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -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) @@ -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) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 2b38dd70b7ea..e132a23ff55f 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -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, @@ -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 @@ -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) @@ -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) @@ -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) @@ -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) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py index 701a2d42ebfb..974985d8b4fc 100644 --- a/mypy/checkstrformat.py +++ b/mypy/checkstrformat.py @@ -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="", module=None, options=self.chk.options, errors=temp_errors diff --git a/mypy/errors.py b/mypy/errors.py index 757f31ba1f6b..ab3d6d130339 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -258,28 +258,17 @@ class Errors: def __init__( self, - show_error_context: bool = False, - show_column_numbers: bool = False, - hide_error_codes: bool = False, - pretty: bool = False, - show_error_end: bool = False, + options: Options, + *, read_source: Callable[[str], list[str] | None] | None = None, - show_absolute_path: bool = False, - many_errors_threshold: int = -1, - options: Options | None = None, + hide_error_codes: bool | None = None, ) -> None: - self.show_error_context = show_error_context - self.show_column_numbers = show_column_numbers - self.hide_error_codes = hide_error_codes - self.show_absolute_path = show_absolute_path - self.pretty = pretty - self.show_error_end = show_error_end - if show_error_end: - assert show_column_numbers, "Inconsistent formatting, must be prevented by argparse" + self.options = options + self.hide_error_codes = ( + hide_error_codes if hide_error_codes is not None else options.hide_error_codes + ) # We use fscache to read source code when showing snippets. self.read_source = read_source - self.many_errors_threshold = many_errors_threshold - self.options = options self.initialize() def initialize(self) -> None: @@ -308,7 +297,7 @@ def set_ignore_prefix(self, prefix: str) -> None: self.ignore_prefix = prefix def simplify_path(self, file: str) -> str: - if self.show_absolute_path: + if self.options.show_absolute_path: return os.path.abspath(file) else: file = os.path.normpath(file) @@ -534,13 +523,13 @@ def add_error_info(self, info: ErrorInfo) -> None: self._add_error_info(file, note) def has_many_errors(self) -> bool: - if self.many_errors_threshold < 0: + if self.options.many_errors_threshold < 0: return False - if len(self.error_info_map) >= self.many_errors_threshold: + if len(self.error_info_map) >= self.options.many_errors_threshold: return True if ( sum(len(errors) for errors in self.error_info_map.values()) - >= self.many_errors_threshold + >= self.options.many_errors_threshold ): return True return False @@ -806,9 +795,9 @@ def format_messages( ) in errors: s = "" if file is not None: - if self.show_column_numbers and line >= 0 and column >= 0: + if self.options.show_column_numbers and line >= 0 and column >= 0: srcloc = f"{file}:{line}:{1 + column}" - if self.show_error_end and end_line >= 0 and end_column >= 0: + if self.options.show_error_end and end_line >= 0 and end_column >= 0: srcloc += f":{end_line}:{end_column}" elif line >= 0: srcloc = f"{file}:{line}" @@ -826,7 +815,7 @@ def format_messages( # displaying duplicate error codes. s = f"{s} [{code.code}]" a.append(s) - if self.pretty: + if self.options.pretty: # Add source code fragment and a location marker. if severity == "error" and source_lines and line > 0: source_line = source_lines[line - 1] @@ -857,7 +846,7 @@ def file_messages(self, path: str) -> list[str]: return [] self.flushed_files.add(path) source_lines = None - if self.pretty: + if self.options.pretty: assert self.read_source source_lines = self.read_source(path) return self.format_messages(self.error_info_map[path], source_lines) @@ -898,7 +887,7 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]: for e in errors: # Report module import context, if different from previous message. - if not self.show_error_context: + if not self.options.show_error_context: pass elif e.import_ctx != prev_import_context: last = len(e.import_ctx) - 1 @@ -923,7 +912,7 @@ def render_messages(self, errors: list[ErrorInfo]) -> list[ErrorTuple]: file = self.simplify_path(e.file) # Report context within a source file. - if not self.show_error_context: + if not self.options.show_error_context: pass elif e.function_or_member != prev_function_or_member or e.type != prev_type: if e.function_or_member is None: diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 9a38d67f30e5..14799214ca6b 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -264,7 +264,7 @@ def parse( if options is None: options = Options() if errors is None: - errors = Errors(hide_error_codes=options.hide_error_codes) + errors = Errors(options) raise_on_error = True errors.set_file(fnam, module, options=options) is_stub_file = fnam.endswith(".pyi") diff --git a/mypy/inspections.py b/mypy/inspections.py index d99e087b93a1..cb695a80eef2 100644 --- a/mypy/inspections.py +++ b/mypy/inspections.py @@ -247,7 +247,9 @@ def expr_type(self, expression: Expression) -> tuple[str, bool]: if expr_type is None: return self.missing_type(expression), False - type_str = format_type(expr_type, verbosity=self.verbosity) + type_str = format_type( + expr_type, self.fg_manager.manager.options, verbosity=self.verbosity + ) return self.add_prefixes(type_str, expression), True def object_type(self) -> Instance: diff --git a/mypy/main.py b/mypy/main.py index 3f5e02ec3f79..6b217e01223f 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -734,6 +734,10 @@ def add_invertible_flag( help="Disable strict Optional checks (inverse: --strict-optional)", ) + add_invertible_flag( + "--force-uppercase-builtins", default=False, help=argparse.SUPPRESS, group=none_group + ) + lint_group = parser.add_argument_group( title="Configuring warnings", description="Detect code that is sound but redundant or problematic.", diff --git a/mypy/messages.py b/mypy/messages.py index bbeb763e2797..4802fa9b36d0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -52,6 +52,7 @@ reverse_builtin_aliases, ) from mypy.operators import op_methods, op_methods_to_symbols +from mypy.options import Options from mypy.subtypes import ( IS_CLASS_OR_STATIC, IS_CLASSVAR, @@ -81,6 +82,7 @@ TypeAliasType, TypedDictType, TypeOfAny, + TypeStrVisitor, TypeType, TypeVarTupleType, TypeVarType, @@ -158,6 +160,7 @@ class MessageBuilder: def __init__(self, errors: Errors, modules: dict[str, MypyFile]) -> None: self.errors = errors + self.options = errors.options self.modules = modules self._disable_type_names = [] @@ -367,7 +370,7 @@ def has_no_attr( self.fail(f'Member "{member}" is not assignable', context) elif member == "__contains__": self.fail( - f"Unsupported right operand type for in ({format_type(original_type)})", + f"Unsupported right operand type for in ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) @@ -380,19 +383,19 @@ def has_no_attr( break elif member == "__neg__": self.fail( - f"Unsupported operand type for unary - ({format_type(original_type)})", + f"Unsupported operand type for unary - ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) elif member == "__pos__": self.fail( - f"Unsupported operand type for unary + ({format_type(original_type)})", + f"Unsupported operand type for unary + ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) elif member == "__invert__": self.fail( - f"Unsupported operand type for ~ ({format_type(original_type)})", + f"Unsupported operand type for ~ ({format_type(original_type, self.options)})", context, code=codes.OPERATOR, ) @@ -402,13 +405,13 @@ def has_no_attr( if isinstance(original_type, CallableType) and original_type.is_type_obj(): self.fail( "The type {} is not generic and not indexable".format( - format_type(original_type) + format_type(original_type, self.options) ), context, ) else: self.fail( - f"Value of type {format_type(original_type)} is not indexable", + f"Value of type {format_type(original_type, self.options)} is not indexable", context, code=codes.INDEX, ) @@ -416,7 +419,7 @@ def has_no_attr( # Indexed set. self.fail( "Unsupported target for indexed assignment ({})".format( - format_type(original_type) + format_type(original_type, self.options) ), context, code=codes.INDEX, @@ -430,7 +433,7 @@ def has_no_attr( self.fail("Cannot call function of unknown type", context, code=codes.OPERATOR) else: self.fail( - message_registry.NOT_CALLABLE.format(format_type(original_type)), + message_registry.NOT_CALLABLE.format(format_type(original_type, self.options)), context, code=codes.OPERATOR, ) @@ -450,7 +453,7 @@ def has_no_attr( and not module_symbol_table[member].module_public ): self.fail( - f"{format_type(original_type, module_names=True)} does not " + f"{format_type(original_type, self.options, module_names=True)} does not " f'explicitly export attribute "{member}"', context, code=codes.ATTR_DEFINED, @@ -472,7 +475,7 @@ def has_no_attr( if matches: self.fail( '{} has no attribute "{}"; maybe {}?{}'.format( - format_type(original_type), + format_type(original_type, self.options), member, pretty_seq(matches, "or"), extra, @@ -484,7 +487,7 @@ def has_no_attr( if not failed: self.fail( '{} has no attribute "{}"{}'.format( - format_type(original_type), member, extra + format_type(original_type, self.options), member, extra ), context, code=codes.ATTR_DEFINED, @@ -492,7 +495,9 @@ def has_no_attr( elif isinstance(original_type, UnionType): # The checker passes "object" in lieu of "None" for attribute # checks, so we manually convert it back. - typ_format, orig_type_format = format_type_distinctly(typ, original_type) + typ_format, orig_type_format = format_type_distinctly( + typ, original_type, options=self.options + ) if typ_format == '"object"' and any( type(item) == NoneType for item in original_type.items ): @@ -507,8 +512,8 @@ def has_no_attr( elif isinstance(original_type, TypeVarType): bound = get_proper_type(original_type.upper_bound) if isinstance(bound, UnionType): - typ_fmt, bound_fmt = format_type_distinctly(typ, bound) - original_type_fmt = format_type(original_type) + typ_fmt, bound_fmt = format_type_distinctly(typ, bound, options=self.options) + original_type_fmt = format_type(original_type, self.options) self.fail( "Item {} of the upper bound {} of type variable {} has no " 'attribute "{}"{}'.format( @@ -519,7 +524,9 @@ def has_no_attr( ) else: self.fail( - '{} has no attribute "{}"{}'.format(format_type(original_type), member, extra), + '{} has no attribute "{}"{}'.format( + format_type(original_type, self.options), member, extra + ), context, code=codes.ATTR_DEFINED, ) @@ -542,13 +549,13 @@ def unsupported_operand_types( if isinstance(left_type, str): left_str = left_type else: - left_str = format_type(left_type) + left_str = format_type(left_type, self.options) right_str = "" if isinstance(right_type, str): right_str = right_type else: - right_str = format_type(right_type) + right_str = format_type(right_type, self.options) if self.are_type_names_disabled(): msg = f"Unsupported operand types for {op} (likely involving Union)" @@ -560,11 +567,11 @@ def unsupported_left_operand(self, op: str, typ: Type, context: Context) -> None if self.are_type_names_disabled(): msg = f"Unsupported left operand type for {op} (some union)" else: - msg = f"Unsupported left operand type for {op} ({format_type(typ)})" + msg = f"Unsupported left operand type for {op} ({format_type(typ, self.options)})" self.fail(msg, context, code=codes.OPERATOR) def not_callable(self, typ: Type, context: Context) -> Type: - self.fail(message_registry.NOT_CALLABLE.format(format_type(typ)), context) + self.fail(message_registry.NOT_CALLABLE.format(format_type(typ, self.options)), context) return AnyType(TypeOfAny.from_error) def untyped_function_call(self, callee: CallableType, context: Context) -> Type: @@ -604,7 +611,7 @@ def incompatible_argument( if callee_name is not None: name = callee_name if callee.bound_args and callee.bound_args[0] is not None: - base = format_type(callee.bound_args[0]) + base = format_type(callee.bound_args[0], self.options) else: base = extract_type(name) @@ -637,7 +644,7 @@ def incompatible_argument( return codes.INDEX else: arg_type_str, callee_type_str = format_type_distinctly( - arg_type, callee.arg_types[n - 1] + arg_type, callee.arg_types[n - 1], options=self.options ) info = ( f" (expression has type {arg_type_str}, " @@ -658,7 +665,7 @@ def incompatible_argument( name = callee_name[1:-1] n -= 1 actual_type_str, expected_type_str = format_type_distinctly( - arg_type, callee.arg_types[0] + arg_type, callee.arg_types[0], options=self.options ) msg = "{} item {} has incompatible type {}; expected {}".format( name.title(), n, actual_type_str, expected_type_str @@ -672,18 +679,18 @@ def incompatible_argument( # don't increase verbosity unless there is need to do so if is_subtype(key_type, expected_key_type): - key_type_str = format_type(key_type) - expected_key_type_str = format_type(expected_key_type) + key_type_str = format_type(key_type, self.options) + expected_key_type_str = format_type(expected_key_type, self.options) else: key_type_str, expected_key_type_str = format_type_distinctly( - key_type, expected_key_type + key_type, expected_key_type, options=self.options ) if is_subtype(value_type, expected_value_type): - value_type_str = format_type(value_type) - expected_value_type_str = format_type(expected_value_type) + value_type_str = format_type(value_type, self.options) + expected_value_type_str = format_type(expected_value_type, self.options) else: value_type_str, expected_value_type_str = format_type_distinctly( - value_type, expected_value_type + value_type, expected_value_type, options=self.options ) msg = "{} entry {} has incompatible type {}: {}; expected {}: {}".format( @@ -697,21 +704,23 @@ def incompatible_argument( code = codes.DICT_ITEM elif callee_name == "": actual_type_str, expected_type_str = map( - strip_quotes, format_type_distinctly(arg_type, callee.arg_types[0]) + strip_quotes, + format_type_distinctly(arg_type, callee.arg_types[0], options=self.options), ) msg = "List comprehension has incompatible type List[{}]; expected List[{}]".format( actual_type_str, expected_type_str ) elif callee_name == "": actual_type_str, expected_type_str = map( - strip_quotes, format_type_distinctly(arg_type, callee.arg_types[0]) + strip_quotes, + format_type_distinctly(arg_type, callee.arg_types[0], options=self.options), ) msg = "Set comprehension has incompatible type Set[{}]; expected Set[{}]".format( actual_type_str, expected_type_str ) elif callee_name == "": actual_type_str, expected_type_str = format_type_distinctly( - arg_type, callee.arg_types[n - 1] + arg_type, callee.arg_types[n - 1], options=self.options ) msg = ( "{} expression in dictionary comprehension has incompatible type {}; " @@ -719,7 +728,7 @@ def incompatible_argument( ).format("Key" if n == 1 else "Value", actual_type_str, expected_type_str) elif callee_name == "": actual_type_str, expected_type_str = format_type_distinctly( - arg_type, callee.arg_types[0] + arg_type, callee.arg_types[0], options=self.options ) msg = "Generator has incompatible item type {}; expected {}".format( actual_type_str, expected_type_str @@ -733,7 +742,7 @@ def incompatible_argument( except IndexError: # Varargs callees expected_type = callee.arg_types[-1] arg_type_str, expected_type_str = format_type_distinctly( - arg_type, expected_type, bare=True + arg_type, expected_type, bare=True, options=self.options ) if arg_kind == ARG_STAR: arg_type_str = "*" + arg_type_str @@ -757,7 +766,7 @@ def incompatible_argument( arg_name = callee.arg_names[m - 1] assert arg_name is not None arg_type_str, expected_type_str = format_type_distinctly( - arg_type.items[arg_name], expected_type, bare=True + arg_type.items[arg_name], expected_type, bare=True, options=self.options ) arg_label = f'"{arg_name}"' if isinstance(outer_context, IndexExpr) and isinstance( @@ -867,7 +876,9 @@ def invalid_index_type( *, code: ErrorCode, ) -> None: - index_str, expected_str = format_type_distinctly(index_type, expected_type) + index_str, expected_str = format_type_distinctly( + index_type, expected_type, options=self.options + ) self.fail( "Invalid index type {} for {}; expected type {}".format( index_str, base_str, expected_str @@ -1037,7 +1048,7 @@ def no_variant_matches_arguments( name_str = f" of {name}" else: name_str = "" - arg_types_str = ", ".join(format_type(arg) for arg in arg_types) + arg_types_str = ", ".join(format_type(arg, self.options) for arg in arg_types) num_args = len(arg_types) if num_args == 0: self.fail( @@ -1060,7 +1071,7 @@ def no_variant_matches_arguments( self.note(f"Possible overload variant{plural_s(len(overload.items))}:", context, code=code) for item in overload.items: - self.note(pretty_callable(item), context, offset=4, code=code) + self.note(pretty_callable(item, self.options), context, offset=4, code=code) def wrong_number_values_to_unpack( self, provided: int, expected: int, context: Context @@ -1081,7 +1092,7 @@ def unpacking_strings_disallowed(self, context: Context) -> None: self.fail("Unpacking a string is disallowed", context) def type_not_iterable(self, type: Type, context: Context) -> None: - self.fail(f"{format_type(type)} object is not iterable", context) + self.fail(f"{format_type(type, self.options)} object is not iterable", context) def possible_missing_await(self, context: Context) -> None: self.note('Maybe you forgot to use "await"?', context) @@ -1164,7 +1175,11 @@ def pretty_callable_or_overload( if decorator is not None: self.note(decorator, context, offset=offset, allow_dups=allow_dups, code=code) self.note( - pretty_callable(tp), context, offset=offset, allow_dups=allow_dups, code=code + pretty_callable(tp, self.options), + context, + offset=offset, + allow_dups=allow_dups, + code=code, ) elif isinstance(tp, Overloaded): self.pretty_overload( @@ -1188,7 +1203,7 @@ def argument_incompatible_with_supertype( secondary_context: Context, ) -> None: target = self.override_target(name, name_in_supertype, supertype) - arg_type_in_supertype_f = format_type_bare(arg_type_in_supertype) + arg_type_in_supertype_f = format_type_bare(arg_type_in_supertype, self.options) self.fail( 'Argument {} of "{}" is incompatible with {}; ' 'supertype defines the argument type as "{}"'.format( @@ -1240,7 +1255,9 @@ def return_type_incompatible_with_supertype( context: Context, ) -> None: target = self.override_target(name, name_in_supertype, supertype) - override_str, original_str = format_type_distinctly(override, original) + override_str, original_str = format_type_distinctly( + override, original, options=self.options + ) self.fail( 'Return type {} of "{}" incompatible with return type {} in {}'.format( override_str, name, original_str, target @@ -1287,7 +1304,7 @@ def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) self.fail("Keywords must be strings", context) else: self.fail( - f"Argument after ** must be a mapping, not {format_type(typ)}", + f"Argument after ** must be a mapping, not {format_type(typ, self.options)}", context, code=codes.ARG_TYPE, ) @@ -1308,7 +1325,7 @@ def first_argument_for_super_must_be_type(self, actual: Type, context: Context) # object. type_str = "a non-type instance" else: - type_str = format_type(actual) + type_str = format_type(actual, self.options) self.fail( f'Argument 1 for "super" must be a type object; got {type_str}', context, @@ -1380,7 +1397,7 @@ def cannot_determine_type_in_base(self, name: str, base: str, context: Context) def no_formal_self(self, name: str, item: CallableType, context: Context) -> None: self.fail( 'Attribute function "%s" with type %s does not accept self argument' - % (name, format_type(item)), + % (name, format_type(item, self.options)), context, ) @@ -1390,7 +1407,7 @@ def incompatible_self_argument( kind = "class attribute function" if is_classmethod else "attribute function" self.fail( 'Invalid self argument %s to %s "%s" with type %s' - % (format_type(arg), kind, name, format_type(sig)), + % (format_type(arg, self.options), kind, name, format_type(sig, self.options)), context, ) @@ -1483,7 +1500,7 @@ def incompatible_typevar_value( ) -> None: self.fail( message_registry.INCOMPATIBLE_TYPEVAR_VALUE.format( - typevar_name, callable_name(callee) or "function", format_type(typ) + typevar_name, callable_name(callee) or "function", format_type(typ, self.options) ), context, code=codes.TYPE_VAR, @@ -1493,7 +1510,7 @@ def dangerous_comparison(self, left: Type, right: Type, kind: str, ctx: Context) left_str = "element" if kind == "container" else "left operand" right_str = "container item" if kind == "container" else "right operand" message = "Non-overlapping {} check ({} type: {}, {} type: {})" - left_typ, right_typ = format_type_distinctly(left, right) + left_typ, right_typ = format_type_distinctly(left, right, options=self.options) self.fail( message.format(kind, left_str, left_typ, right_str, right_typ), ctx, @@ -1551,7 +1568,9 @@ def warn_both_operands_are_from_unions(self, context: Context) -> None: def warn_operand_was_from_union(self, side: str, original: Type, context: Context) -> None: self.note( - f"{side} operand is of type {format_type(original)}", context, code=codes.OPERATOR + f"{side} operand is of type {format_type(original, self.options)}", + context, + code=codes.OPERATOR, ) def operator_method_signatures_overlap( @@ -1565,7 +1584,10 @@ def operator_method_signatures_overlap( self.fail( 'Signatures of "{}" of "{}" and "{}" of {} ' "are unsafely overlapping".format( - reverse_method, reverse_class.name, forward_method, format_type(forward_class) + reverse_method, + reverse_class.name, + forward_method, + format_type(forward_class, self.options), ), context, ) @@ -1577,20 +1599,28 @@ def signatures_incompatible(self, method: str, other_method: str, context: Conte self.fail(f'Signatures of "{method}" and "{other_method}" are incompatible', context) def yield_from_invalid_operand_type(self, expr: Type, context: Context) -> Type: - text = format_type(expr) if format_type(expr) != "object" else expr + text = ( + format_type(expr, self.options) + if format_type(expr, self.options) != "object" + else expr + ) self.fail(f'"yield from" can\'t be applied to {text}', context) return AnyType(TypeOfAny.from_error) def invalid_signature(self, func_type: Type, context: Context) -> None: - self.fail(f"Invalid signature {format_type(func_type)}", context) + self.fail(f"Invalid signature {format_type(func_type, self.options)}", context) def invalid_signature_for_special_method( self, func_type: Type, context: Context, method_name: str ) -> None: - self.fail(f'Invalid signature {format_type(func_type)} for "{method_name}"', context) + self.fail( + f'Invalid signature {format_type(func_type, self.options)} for "{method_name}"', + context, + ) def reveal_type(self, typ: Type, context: Context) -> None: - self.note(f'Revealed type is "{typ}"', context) + visitor = TypeStrVisitor(options=self.options) + self.note(f'Revealed type is "{typ.accept(visitor)}"', context) def reveal_locals(self, type_map: dict[str, Type | None], context: Context) -> None: # To ensure that the output is predictable on Python < 3.6, @@ -1599,27 +1629,34 @@ def reveal_locals(self, type_map: dict[str, Type | None], context: Context) -> N if sorted_locals: self.note("Revealed local types are:", context) for k, v in sorted_locals.items(): - self.note(f" {k}: {v}", context) + visitor = TypeStrVisitor(options=self.options) + self.note(f" {k}: {v.accept(visitor) if v is not None else None}", context) else: self.note("There are no locals to reveal", context) def unsupported_type_type(self, item: Type, context: Context) -> None: - self.fail(f'Cannot instantiate type "Type[{format_type_bare(item)}]"', context) + self.fail( + f'Cannot instantiate type "Type[{format_type_bare(item, self.options)}]"', context + ) def redundant_cast(self, typ: Type, context: Context) -> None: - self.fail(f"Redundant cast to {format_type(typ)}", context, code=codes.REDUNDANT_CAST) + self.fail( + f"Redundant cast to {format_type(typ, self.options)}", + context, + code=codes.REDUNDANT_CAST, + ) def assert_type_fail(self, source_type: Type, target_type: Type, context: Context) -> None: self.fail( - f"Expression is of type {format_type(source_type)}, " - f"not {format_type(target_type)}", + f"Expression is of type {format_type(source_type, self.options)}, " + f"not {format_type(target_type, self.options)}", context, code=codes.ASSERT_TYPE, ) def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> None: self.fail( - f"{prefix} becomes {format_type(typ)} due to an unfollowed import", + f"{prefix} becomes {format_type(typ, self.options)} due to an unfollowed import", ctx, code=codes.NO_ANY_UNIMPORTED, ) @@ -1684,7 +1721,7 @@ def unexpected_typeddict_keys( if missing: self.fail( "Missing {} for TypedDict {}".format( - format_key_list(missing, short=True), format_type(typ) + format_key_list(missing, short=True), format_type(typ, self.options) ), context, code=codes.TYPEDDICT_ITEM, @@ -1693,7 +1730,7 @@ def unexpected_typeddict_keys( if extra: self.fail( "Extra {} for TypedDict {}".format( - format_key_list(extra, short=True), format_type(typ) + format_key_list(extra, short=True), format_type(typ, self.options) ), context, code=codes.TYPEDDICT_UNKNOWN_KEY, @@ -1739,7 +1776,9 @@ def typeddict_key_not_found( else: err_code = codes.TYPEDDICT_UNKNOWN_KEY if setitem else codes.TYPEDDICT_ITEM self.fail( - f'TypedDict {format_type(typ)} has no key "{item_name}"', context, code=err_code + f'TypedDict {format_type(typ, self.options)} has no key "{item_name}"', + context, + code=err_code, ) matches = best_matches(item_name, typ.items.keys(), n=3) if matches: @@ -1748,7 +1787,7 @@ def typeddict_key_not_found( ) def typeddict_context_ambiguous(self, types: list[TypedDictType], context: Context) -> None: - formatted_types = ", ".join(list(format_type_distinctly(*types))) + formatted_types = ", ".join(list(format_type_distinctly(*types, options=self.options))) self.fail( f"Type of TypedDict is ambiguous, none of ({formatted_types}) matches cleanly", context ) @@ -1760,7 +1799,8 @@ def typeddict_key_cannot_be_deleted( self.fail(f'TypedDict key "{item_name}" cannot be deleted', context) else: self.fail( - f'Key "{item_name}" of TypedDict {format_type(typ)} cannot be deleted', context + f'Key "{item_name}" of TypedDict {format_type(typ, self.options)} cannot be deleted', + context, ) def typeddict_setdefault_arguments_inconsistent( @@ -1768,7 +1808,7 @@ def typeddict_setdefault_arguments_inconsistent( ) -> None: msg = 'Argument 2 to "setdefault" of "TypedDict" has incompatible type {}; expected {}' self.fail( - msg.format(format_type(default), format_type(expected)), + msg.format(format_type(default, self.options), format_type(expected, self.options)), context, code=codes.TYPEDDICT_ITEM, ) @@ -1781,11 +1821,13 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): message = 'Expression has type "Any"' else: - message = f'Expression type contains "Any" (has type {format_type(typ)})' + message = f'Expression type contains "Any" (has type {format_type(typ, self.options)})' self.fail(message, context) def incorrectly_returning_any(self, typ: Type, context: Context) -> None: - message = f"Returning Any from function declared to return {format_type(typ)}" + message = ( + f"Returning Any from function declared to return {format_type(typ, self.options)}" + ) self.fail(message, context, code=codes.NO_ANY_RETURN) def incorrect__exit__return(self, context: Context) -> None: @@ -1812,7 +1854,8 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None: self.fail("Function is untyped after decorator transformation", context) else: self.fail( - f'Type of decorated function contains type "Any" ({format_type(typ)})', context + f'Type of decorated function contains type "Any" ({format_type(typ, self.options)})', + context, ) def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None: @@ -1831,14 +1874,14 @@ def bad_proto_variance( def concrete_only_assign(self, typ: Type, context: Context) -> None: self.fail( - f"Can only assign concrete classes to a variable of type {format_type(typ)}", + f"Can only assign concrete classes to a variable of type {format_type(typ, self.options)}", context, code=codes.TYPE_ABSTRACT, ) def concrete_only_call(self, typ: Type, context: Context) -> None: self.fail( - f"Only concrete class can be given where {format_type(typ)} is expected", + f"Only concrete class can be given where {format_type(typ, self.options)} is expected", context, code=codes.TYPE_ABSTRACT, ) @@ -1864,7 +1907,8 @@ def note_call( ) -> None: self.note( '"{}.__call__" has type {}'.format( - format_type_bare(subtype), format_type(call, verbosity=1) + format_type_bare(subtype, self.options), + format_type(call, self.options, verbosity=1), ), context, code=code, @@ -2006,7 +2050,7 @@ def report_protocol_problems( or not subtype.type.defn.type_vars or not supertype.type.defn.type_vars ): - type_name = format_type(subtype, module_names=True) + type_name = format_type(subtype, self.options, module_names=True) self.note(f"Following member(s) of {type_name} have conflicts:", context, code=code) for name, got, exp in conflict_types[:MAX_ITEMS]: exp = get_proper_type(exp) @@ -2015,7 +2059,9 @@ def report_protocol_problems( got, (CallableType, Overloaded) ): self.note( - "{}: expected {}, got {}".format(name, *format_type_distinctly(exp, got)), + "{}: expected {}, got {}".format( + name, *format_type_distinctly(exp, got, options=self.options) + ), context, offset=OFFSET, code=code, @@ -2024,7 +2070,7 @@ def report_protocol_problems( self.note("Expected:", context, offset=OFFSET, code=code) if isinstance(exp, CallableType): self.note( - pretty_callable(exp, skip_self=class_obj or is_module), + pretty_callable(exp, self.options, skip_self=class_obj or is_module), context, offset=2 * OFFSET, code=code, @@ -2037,7 +2083,7 @@ def report_protocol_problems( self.note("Got:", context, offset=OFFSET, code=code) if isinstance(got, CallableType): self.note( - pretty_callable(got, skip_self=class_obj or is_module), + pretty_callable(got, self.options, skip_self=class_obj or is_module), context, offset=2 * OFFSET, code=code, @@ -2122,7 +2168,7 @@ def pretty_overload( self.note(decorator, context, offset=offset, allow_dups=allow_dups, code=code) self.note( - pretty_callable(item, skip_self=skip_self), + pretty_callable(item, self.options, skip_self=skip_self), context, offset=offset, allow_dups=allow_dups, @@ -2195,11 +2241,14 @@ def format_long_tuple_type(self, typ: TupleType) -> str: """Format very long tuple type using an ellipsis notation""" item_cnt = len(typ.items) if item_cnt > 10: - return "Tuple[{}, {}, ... <{} more items>]".format( - format_type_bare(typ.items[0]), format_type_bare(typ.items[1]), str(item_cnt - 2) + return "{}[{}, {}, ... <{} more items>]".format( + "tuple" if self.options.use_lowercase_names() else "Tuple", + format_type_bare(typ.items[0], self.options), + format_type_bare(typ.items[1], self.options), + str(item_cnt - 2), ) else: - return format_type_bare(typ) + return format_type_bare(typ, self.options) def generate_incompatible_tuple_error( self, @@ -2210,13 +2259,15 @@ def generate_incompatible_tuple_error( ) -> None: """Generate error message for individual incompatible tuple pairs""" error_cnt = 0 - notes = [] # List[str] + notes: list[str] = [] for i, (lhs_t, rhs_t) in enumerate(zip(lhs_types, rhs_types)): if not is_subtype(lhs_t, rhs_t): if error_cnt < 3: notes.append( "Expression tuple item {} has type {}; {} expected; ".format( - str(i), format_type(rhs_t), format_type(lhs_t) + str(i), + format_type(rhs_t, self.options), + format_type(lhs_t, self.options), ) ) error_cnt += 1 @@ -2288,7 +2339,11 @@ def format_callable_args( def format_type_inner( - typ: Type, verbosity: int, fullnames: set[str] | None, module_names: bool = False + typ: Type, + verbosity: int, + options: Options, + fullnames: set[str] | None, + module_names: bool = False, ) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2299,7 +2354,7 @@ def format_type_inner( """ def format(typ: Type) -> str: - return format_type_inner(typ, verbosity, fullnames) + return format_type_inner(typ, verbosity, options, fullnames) def format_list(types: Sequence[Type]) -> str: return ", ".join(format(typ) for typ in types) @@ -2336,7 +2391,10 @@ def format_literal_value(typ: LiteralType) -> str: if itype.type.fullname == "typing._SpecialForm": # This is not a real type but used for some typing-related constructs. return "" - if verbosity >= 2 or (fullnames and itype.type.fullname in fullnames): + if itype.type.fullname in reverse_builtin_aliases and not options.use_lowercase_names(): + alias = reverse_builtin_aliases[itype.type.fullname] + base_str = alias.split(".")[-1] + elif verbosity >= 2 or (fullnames and itype.type.fullname in fullnames): base_str = itype.type.fullname else: base_str = itype.type.name @@ -2345,11 +2403,7 @@ def format_literal_value(typ: LiteralType) -> str: return base_str elif itype.type.fullname == "builtins.tuple": item_type_str = format(itype.args[0]) - return f"Tuple[{item_type_str}, ...]" - elif itype.type.fullname in reverse_builtin_aliases: - alias = reverse_builtin_aliases[itype.type.fullname] - alias = alias.split(".")[-1] - return f"{alias}[{format_list(itype.args)}]" + return f"{'tuple' if options.use_lowercase_names() else 'Tuple'}[{item_type_str}, ...]" else: # There are type arguments. Convert the arguments to strings. return f"{base_str}[{format_list(itype.args)}]" @@ -2375,7 +2429,10 @@ def format_literal_value(typ: LiteralType) -> str: # Prefer the name of the fallback class (if not tuple), as it's more informative. if typ.partial_fallback.type.fullname != "builtins.tuple": return format(typ.partial_fallback) - s = f"Tuple[{format_list(typ.items)}]" + if options.use_lowercase_names(): + s = f"tuple[{format_list(typ.items)}]" + else: + s = f"Tuple[{format_list(typ.items)}]" return s elif isinstance(typ, TypedDictType): # If the TypedDictType is named, return the name @@ -2457,7 +2514,7 @@ def format_literal_value(typ: LiteralType) -> str: # error messages. return "overloaded function" elif isinstance(typ, UnboundType): - return str(typ) + return typ.accept(TypeStrVisitor(options=options)) elif isinstance(typ, Parameters): args = format_callable_args(typ.arg_types, typ.arg_kinds, typ.arg_names, format, verbosity) return f"[{args}]" @@ -2514,7 +2571,9 @@ def find_type_overlaps(*types: Type) -> set[str]: return overlaps -def format_type(typ: Type, verbosity: int = 0, module_names: bool = False) -> str: +def format_type( + typ: Type, options: Options, verbosity: int = 0, module_names: bool = False +) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2525,10 +2584,12 @@ def format_type(typ: Type, verbosity: int = 0, module_names: bool = False) -> st modification of the formatted string is required, callers should use format_type_bare. """ - return quote_type_string(format_type_bare(typ, verbosity, module_names)) + return quote_type_string(format_type_bare(typ, options, verbosity, module_names)) -def format_type_bare(typ: Type, verbosity: int = 0, module_names: bool = False) -> str: +def format_type_bare( + typ: Type, options: Options, verbosity: int = 0, module_names: bool = False +) -> str: """ Convert a type to a relatively short string suitable for error messages. @@ -2540,10 +2601,10 @@ def format_type_bare(typ: Type, verbosity: int = 0, module_names: bool = False) instead. (The caller may want to use quote_type_string after processing has happened, to maintain consistent quoting in messages.) """ - return format_type_inner(typ, verbosity, find_type_overlaps(typ), module_names) + return format_type_inner(typ, verbosity, options, find_type_overlaps(typ), module_names) -def format_type_distinctly(*types: Type, bare: bool = False) -> tuple[str, ...]: +def format_type_distinctly(*types: Type, options: Options, bare: bool = False) -> tuple[str, ...]: """Jointly format types to distinct strings. Increase the verbosity of the type strings until they become distinct @@ -2558,7 +2619,8 @@ def format_type_distinctly(*types: Type, bare: bool = False) -> tuple[str, ...]: overlapping = find_type_overlaps(*types) for verbosity in range(2): strs = [ - format_type_inner(type, verbosity=verbosity, fullnames=overlapping) for type in types + format_type_inner(type, verbosity=verbosity, options=options, fullnames=overlapping) + for type in types ] if len(set(strs)) == len(strs): break @@ -2578,7 +2640,7 @@ def pretty_class_or_static_decorator(tp: CallableType) -> str | None: return None -def pretty_callable(tp: CallableType, skip_self: bool = False) -> str: +def pretty_callable(tp: CallableType, options: Options, skip_self: bool = False) -> str: """Return a nice easily-readable representation of a callable type. For example: def [T <: int] f(self, x: int, y: T) -> None @@ -2604,7 +2666,7 @@ def [T <: int] f(self, x: int, y: T) -> None name = tp.arg_names[i] if name: s += name + ": " - type_str = format_type_bare(tp.arg_types[i]) + type_str = format_type_bare(tp.arg_types[i], options) if tp.arg_kinds[i] == ARG_STAR2 and tp.unpack_kwargs: type_str = f"Unpack[{type_str}]" s += type_str @@ -2646,9 +2708,9 @@ def [T <: int] f(self, x: int, y: T) -> None s += " -> " if tp.type_guard is not None: - s += f"TypeGuard[{format_type_bare(tp.type_guard)}]" + s += f"TypeGuard[{format_type_bare(tp.type_guard, options)}]" else: - s += format_type_bare(tp.ret_type) + s += format_type_bare(tp.ret_type, options) if tp.variables: tvars = [] @@ -2659,11 +2721,12 @@ def [T <: int] f(self, x: int, y: T) -> None isinstance(upper_bound, Instance) and upper_bound.type.fullname != "builtins.object" ): - tvars.append(f"{tvar.name} <: {format_type_bare(upper_bound)}") + tvars.append(f"{tvar.name} <: {format_type_bare(upper_bound, options)}") elif tvar.values: tvars.append( "{} in ({})".format( - tvar.name, ", ".join([format_type_bare(tp) for tp in tvar.values]) + tvar.name, + ", ".join([format_type_bare(tp, options) for tp in tvar.values]), ) ) else: diff --git a/mypy/nodes.py b/mypy/nodes.py index 83d8d319f725..0ec0d81cfbc8 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -25,6 +25,7 @@ from mypy_extensions import trait import mypy.strconv +from mypy.options import Options from mypy.util import short_type from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor @@ -173,7 +174,7 @@ def set_line( def get_nongen_builtins(python_version: tuple[int, int]) -> dict[str, str]: - # After 3.9 with pep585 generic builtins are allowed. + # After 3.9 with pep585 generic builtins are allowed return _nongen_builtins if python_version < (3, 9) else {} @@ -190,11 +191,16 @@ class Node(Context): __slots__ = () def __str__(self) -> str: - ans = self.accept(mypy.strconv.StrConv()) + ans = self.accept(mypy.strconv.StrConv(options=Options())) if ans is None: return repr(self) return ans + def str_with_options(self, options: Options) -> str: + ans = self.accept(mypy.strconv.StrConv(options=options)) + assert ans + return ans + def accept(self, visitor: NodeVisitor[T]) -> T: raise RuntimeError("Not implemented") @@ -3183,22 +3189,21 @@ def __str__(self) -> str: This includes the most important information about the type. """ - return self.dump() + options = Options() + return self.dump( + str_conv=mypy.strconv.StrConv(options=options), + type_str_conv=mypy.types.TypeStrVisitor(options=options), + ) def dump( - self, - str_conv: mypy.strconv.StrConv | None = None, - type_str_conv: mypy.types.TypeStrVisitor | None = None, + self, str_conv: mypy.strconv.StrConv, type_str_conv: mypy.types.TypeStrVisitor ) -> str: """Return a string dump of the contents of the TypeInfo.""" - if not str_conv: - str_conv = mypy.strconv.StrConv() + base: str = "" def type_str(typ: mypy.types.Type) -> str: - if type_str_conv: - return typ.accept(type_str_conv) - return str(typ) + return typ.accept(type_str_conv) head = "TypeInfo" + str_conv.format_id(self) if self.bases: diff --git a/mypy/options.py b/mypy/options.py index 077e0d4ed90a..27fa1bc9ef13 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -346,6 +346,13 @@ def __init__(self) -> None: self.disable_bytearray_promotion = False self.disable_memoryview_promotion = False + self.force_uppercase_builtins = False + + def use_lowercase_names(self) -> bool: + if self.python_version >= (3, 9): + return not self.force_uppercase_builtins + return False + # To avoid breaking plugin compatibility, keep providing new_semantic_analyzer @property def new_semantic_analyzer(self) -> bool: diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 80c2ff3d3325..281494cb05e9 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -973,7 +973,7 @@ def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> Callabl inst_type = get_proper_type(inst_type) if isinstance(inst_type, AnyType): return ctx.default_signature # evolve(Any, ....) -> Any - inst_type_str = format_type_bare(inst_type) + inst_type_str = format_type_bare(inst_type, ctx.api.options) attrs_type, attrs_init_type = _get_attrs_cls_and_init(inst_type) if attrs_type is None or attrs_init_type is None: diff --git a/mypy/plugins/ctypes.py b/mypy/plugins/ctypes.py index edfbe506fcca..b687c40bbb09 100644 --- a/mypy/plugins/ctypes.py +++ b/mypy/plugins/ctypes.py @@ -123,7 +123,9 @@ def array_constructor_callback(ctx: mypy.plugin.FunctionContext) -> Type: ctx.api.msg.fail( "Array constructor argument {} of type {}" " is not convertible to the array element type {}".format( - arg_num, format_type(arg_type), format_type(et) + arg_num, + format_type(arg_type, ctx.api.options), + format_type(et, ctx.api.options), ), ctx.context, ) @@ -134,7 +136,9 @@ def array_constructor_callback(ctx: mypy.plugin.FunctionContext) -> Type: ctx.api.msg.fail( "Array constructor argument {} of type {}" " is not convertible to the array element type {}".format( - arg_num, format_type(arg_type), format_type(it) + arg_num, + format_type(arg_type, ctx.api.options), + format_type(it, ctx.api.options), ), ctx.context, ) @@ -209,7 +213,9 @@ def array_value_callback(ctx: mypy.plugin.AttributeContext) -> Type: else: ctx.api.msg.fail( 'Array attribute "value" is only available' - ' with element type "c_char" or "c_wchar", not {}'.format(format_type(et)), + ' with element type "c_char" or "c_wchar", not {}'.format( + format_type(et, ctx.api.options) + ), ctx.context, ) return make_simplified_union(types) @@ -232,7 +238,7 @@ def array_raw_callback(ctx: mypy.plugin.AttributeContext) -> Type: else: ctx.api.msg.fail( 'Array attribute "raw" is only available' - ' with element type "c_char", not {}'.format(format_type(et)), + ' with element type "c_char", not {}'.format(format_type(et, ctx.api.options)), ctx.context, ) return make_simplified_union(types) diff --git a/mypy/plugins/singledispatch.py b/mypy/plugins/singledispatch.py index ecfad83bde93..a44493f900b1 100644 --- a/mypy/plugins/singledispatch.py +++ b/mypy/plugins/singledispatch.py @@ -5,6 +5,7 @@ from mypy.messages import format_type from mypy.nodes import ARG_POS, Argument, Block, ClassDef, Context, SymbolTable, TypeInfo, Var +from mypy.options import Options from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext, MethodSigContext from mypy.plugins.common import add_method_to_class from mypy.subtypes import is_subtype @@ -142,7 +143,7 @@ def singledispatch_register_callback(ctx: MethodContext) -> Type: return register_callable elif isinstance(first_arg_type, CallableType): # TODO: do more checking for registered functions - register_function(ctx, ctx.type, first_arg_type) + register_function(ctx, ctx.type, first_arg_type, ctx.api.options) # The typeshed stubs for register say that the function returned is Callable[..., T], even # though the function returned is the same as the one passed in. We return the type of the # function so that mypy can properly type check cases where the registered function is used @@ -154,7 +155,11 @@ def singledispatch_register_callback(ctx: MethodContext) -> Type: def register_function( - ctx: PluginContext, singledispatch_obj: Instance, func: Type, register_arg: Type | None = None + ctx: PluginContext, + singledispatch_obj: Instance, + func: Type, + options: Options, + register_arg: Type | None = None, ) -> None: """Register a function""" @@ -178,7 +183,7 @@ def register_function( fail( ctx, "Dispatch type {} must be subtype of fallback function first argument {}".format( - format_type(dispatch_type), format_type(fallback_dispatch_type) + format_type(dispatch_type, options), format_type(fallback_dispatch_type, options) ), func.definition, ) @@ -201,7 +206,9 @@ def call_singledispatch_function_after_register_argument(ctx: MethodContext) -> type_args = RegisterCallableInfo(*register_callable.args) # type: ignore[arg-type] func = get_first_arg(ctx.arg_types) if func is not None: - register_function(ctx, type_args.singledispatch_obj, func, type_args.register_type) + register_function( + ctx, type_args.singledispatch_obj, func, ctx.api.options, type_args.register_type + ) # see call to register_function in the callback for register return func return ctx.default_return_type diff --git a/mypy/semanal.py b/mypy/semanal.py index 547bf4863edd..8d17c84b130b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -717,7 +717,7 @@ def create_alias(self, tree: MypyFile, target_name: str, alias: str, name: str) target = self.named_type_or_none(target_name, []) assert target is not None # Transform List to List[Any], etc. - fix_instance_types(target, self.fail, self.note, self.options.python_version) + fix_instance_types(target, self.fail, self.note, self.options) alias_node = TypeAlias( target, alias, @@ -3535,7 +3535,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # if the expected number of arguments is non-zero, so that aliases like A = List work. # However, eagerly expanding aliases like Text = str is a nice performance optimization. no_args = isinstance(res, Instance) and not res.args # type: ignore[misc] - fix_instance_types(res, self.fail, self.note, self.options.python_version) + fix_instance_types(res, self.fail, self.note, self.options) # Aliases defined within functions can't be accessed outside # the function, since the symbol table will no longer # exist. Work around by expanding them eagerly when used. diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index cb1055a62186..a8380309d310 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -105,7 +105,11 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: else: if old_type is not None: message = "Argument 2 to NewType(...) must be subclassable (got {})" - self.fail(message.format(format_type(old_type)), s, code=codes.VALID_NEWTYPE) + self.fail( + message.format(format_type(old_type, self.options)), + s, + code=codes.VALID_NEWTYPE, + ) # Otherwise the error was already reported. old_type = AnyType(TypeOfAny.from_error) object_type = self.api.named_type("builtins.object") diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index b9965236c379..5d66c03aa33e 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -141,7 +141,9 @@ def validate_args( is_error = True self.fail( message_registry.INVALID_TYPEVAR_ARG_BOUND.format( - format_type(arg), name, format_type(tvar.upper_bound) + format_type(arg, self.options), + name, + format_type(tvar.upper_bound, self.options), ), ctx, code=codes.TYPE_VAR, @@ -152,7 +154,7 @@ def validate_args( ): self.fail( "Can only replace ParamSpec with a parameter types list or" - f" another ParamSpec, got {format_type(arg)}", + f" another ParamSpec, got {format_type(arg, self.options)}", ctx, ) return is_error @@ -170,7 +172,9 @@ def visit_unpack_type(self, typ: UnpackType) -> None: # TODO: Infer something when it can't be unpacked to allow rest of # typechecking to work. - self.fail(message_registry.INVALID_UNPACK.format(proper_type), typ) + self.fail( + message_registry.INVALID_UNPACK.format(format_type(proper_type, self.options)), typ + ) def check_type_var_values( self, name: str, actuals: list[Type], arg_name: str, valids: list[Type], context: Context diff --git a/mypy/strconv.py b/mypy/strconv.py index b2e9da5dbf6a..e24be7fe35ca 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -7,11 +7,13 @@ from typing import TYPE_CHECKING, Any, Sequence import mypy.nodes +from mypy.options import Options from mypy.util import IdMapper, short_type from mypy.visitor import NodeVisitor if TYPE_CHECKING: import mypy.patterns + import mypy.types class StrConv(NodeVisitor[str]): @@ -26,12 +28,20 @@ class StrConv(NodeVisitor[str]): IntExpr(1))) """ - def __init__(self, show_ids: bool = False) -> None: + __slots__ = ["options", "show_ids", "id_mapper"] + + def __init__(self, *, show_ids: bool = False, options: Options) -> None: + self.options = options self.show_ids = show_ids self.id_mapper: IdMapper | None = None if show_ids: self.id_mapper = IdMapper() + def stringify_type(self, t: mypy.types.Type) -> str: + import mypy.types + + return t.accept(mypy.types.TypeStrVisitor(id_mapper=self.id_mapper, options=self.options)) + def get_id(self, o: object) -> int | None: if self.id_mapper: return self.id_mapper.id(o) @@ -168,11 +178,11 @@ def visit_class_def(self, o: mypy.nodes.ClassDef) -> str: if o.type_vars: a.insert(1, ("TypeVars", o.type_vars)) if o.metaclass: - a.insert(1, f"Metaclass({o.metaclass})") + a.insert(1, f"Metaclass({o.metaclass.accept(self)})") if o.decorators: a.insert(1, ("Decorators", o.decorators)) if o.info and o.info._promote: - a.insert(1, f"Promote({o.info._promote})") + a.insert(1, f"Promote([{','.join(self.stringify_type(p) for p in o.info._promote)}])") if o.info and o.info.tuple_type: a.insert(1, ("TupleType", [o.info.tuple_type])) if o.info and o.info.fallback_to_any: @@ -473,7 +483,7 @@ def visit_type_var_expr(self, o: mypy.nodes.TypeVarExpr) -> str: if o.values: a += [("Values", o.values)] if not mypy.types.is_named_instance(o.upper_bound, "builtins.object"): - a += [f"UpperBound({o.upper_bound})"] + a += [f"UpperBound({self.stringify_type(o.upper_bound)})"] return self.dump(a, o) def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> str: @@ -485,7 +495,7 @@ def visit_paramspec_expr(self, o: mypy.nodes.ParamSpecExpr) -> str: if o.variance == mypy.nodes.CONTRAVARIANT: a += ["Variance(CONTRAVARIANT)"] if not mypy.types.is_named_instance(o.upper_bound, "builtins.object"): - a += [f"UpperBound({o.upper_bound})"] + a += [f"UpperBound({self.stringify_type(o.upper_bound)})"] return self.dump(a, o) def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> str: @@ -497,14 +507,14 @@ def visit_type_var_tuple_expr(self, o: mypy.nodes.TypeVarTupleExpr) -> str: if o.variance == mypy.nodes.CONTRAVARIANT: a += ["Variance(CONTRAVARIANT)"] if not mypy.types.is_named_instance(o.upper_bound, "builtins.object"): - a += [f"UpperBound({o.upper_bound})"] + a += [f"UpperBound({self.stringify_type(o.upper_bound)})"] return self.dump(a, o) def visit_type_alias_expr(self, o: mypy.nodes.TypeAliasExpr) -> str: - return f"TypeAliasExpr({o.type})" + return f"TypeAliasExpr({self.stringify_type(o.type)})" def visit_namedtuple_expr(self, o: mypy.nodes.NamedTupleExpr) -> str: - return f"NamedTupleExpr:{o.line}({o.info.name}, {o.info.tuple_type})" + return f"NamedTupleExpr:{o.line}({o.info.name}, {self.stringify_type(o.info.tuple_type) if o.info.tuple_type is not None else None})" def visit_enum_call_expr(self, o: mypy.nodes.EnumCallExpr) -> str: return f"EnumCallExpr:{o.line}({o.info.name}, {o.items})" @@ -513,7 +523,7 @@ def visit_typeddict_expr(self, o: mypy.nodes.TypedDictExpr) -> str: return f"TypedDictExpr:{o.line}({o.info.name})" def visit__promote_expr(self, o: mypy.nodes.PromoteExpr) -> str: - return f"PromoteExpr:{o.line}({o.type})" + return f"PromoteExpr:{o.line}({self.stringify_type(o.type)})" def visit_newtype_expr(self, o: mypy.nodes.NewTypeExpr) -> str: return f"NewTypeExpr:{o.line}({o.name}, {self.dump([o.old_type], o)})" @@ -614,7 +624,9 @@ def dump_tagged(nodes: Sequence[object], tag: str | None, str_conv: StrConv) -> elif isinstance(n, mypy.nodes.Node): a.append(indent(n.accept(str_conv), 2)) elif isinstance(n, Type): - a.append(indent(n.accept(TypeStrVisitor(str_conv.id_mapper)), 2)) + a.append( + indent(n.accept(TypeStrVisitor(str_conv.id_mapper, options=str_conv.options)), 2) + ) elif n is not None: a.append(indent(str(n), 2)) if tag: diff --git a/mypy/stubgen.py b/mypy/stubgen.py index ce2b3b8d8880..c13096189f7e 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -307,7 +307,7 @@ class AnnotationPrinter(TypeStrVisitor): # TODO: Generate valid string representation for callable types. # TODO: Use short names for Instances. def __init__(self, stubgen: StubGenerator) -> None: - super().__init__() + super().__init__(options=mypy.options.Options()) self.stubgen = stubgen def visit_any(self, t: AnyType) -> str: @@ -1601,7 +1601,7 @@ def parse_source_file(mod: StubSource, mypy_options: MypyOptions) -> None: with open(mod.path, "rb") as f: data = f.read() source = mypy.util.decode_python_encoding(data) - errors = Errors() + errors = Errors(mypy_options) mod.ast = mypy.parse.parse( source, fnam=mod.path, module=mod.module, errors=errors, options=mypy_options ) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index a4b572c206c8..ca25c2a870e5 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1106,7 +1106,7 @@ def verify_overloadedfuncdef( "is inconsistent, " + message, stub, runtime, - stub_desc=str(stub.type) + f"\nInferred signature: {stub_sig}", + stub_desc=(str(stub.type)) + f"\nInferred signature: {stub_sig}", runtime_desc="def " + str(signature), ) diff --git a/mypy/suggestions.py b/mypy/suggestions.py index 9ac033ba3bdf..2e3744025325 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -54,6 +54,7 @@ TypeInfo, reverse_builtin_aliases, ) +from mypy.options import Options from mypy.plugin import FunctionContext, MethodContext, Plugin from mypy.server.update import FineGrainedBuildManager from mypy.state import state @@ -735,7 +736,7 @@ def format_signature(self, sig: PyAnnotateSignature) -> str: def format_type(self, cur_module: str | None, typ: Type) -> str: if self.use_fixme and isinstance(get_proper_type(typ), AnyType): return self.use_fixme - return typ.accept(TypeFormatter(cur_module, self.graph)) + return typ.accept(TypeFormatter(cur_module, self.graph, self.manager.options)) def score_type(self, t: Type, arg_pos: bool) -> int: """Generate a score for a type that we use to pick which type to use. @@ -809,8 +810,8 @@ class TypeFormatter(TypeStrVisitor): """Visitor used to format types""" # TODO: Probably a lot - def __init__(self, module: str | None, graph: Graph) -> None: - super().__init__() + def __init__(self, module: str | None, graph: Graph, options: Options) -> None: + super().__init__(options=options) self.module = module self.graph = graph diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index 177ea5be2298..52b2497f0c3c 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -382,6 +382,7 @@ def parse_options( options.strict_optional = False options.error_summary = False options.hide_error_codes = True + options.force_uppercase_builtins = True # Allow custom python version to override testfile_pyversion. if all(flag.split("=")[0] not in ["--python-version", "-2", "--py2"] for flag in flag_list): diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 5f128283a190..ef27f1c3f4ed 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -125,6 +125,8 @@ def run_case_once( options.hide_error_codes = False if "abstract" not in testcase.file: options.allow_empty_bodies = not testcase.name.endswith("_no_empty") + if "lowercase" not in testcase.file: + options.force_uppercase_builtins = True if incremental_step and options.incremental: # Don't overwrite # flags: --no-incremental in incremental test cases diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 2e8b0dc9a1cd..48e99939e6ba 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -61,6 +61,8 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: args.append("--hide-error-codes") if "--disallow-empty-bodies" not in args: args.append("--allow-empty-bodies") + if "--no-force-uppercase-builtins" not in args: + args.append("--force-uppercase-builtins") # Type check the program. fixed = [python3_path, "-m", "mypy"] env = os.environ.copy() diff --git a/mypy/test/testgraph.py b/mypy/test/testgraph.py index b145d92aea6c..ce7697142ff2 100644 --- a/mypy/test/testgraph.py +++ b/mypy/test/testgraph.py @@ -41,9 +41,9 @@ def test_scc(self) -> None: assert_equal(sccs, {frozenset({"A"}), frozenset({"B", "C"}), frozenset({"D"})}) def _make_manager(self) -> BuildManager: - errors = Errors() options = Options() options.use_builtins_fixtures = True + errors = Errors(options) fscache = FileSystemCache() search_paths = SearchPaths((), (), (), ()) manager = BuildManager( diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 11e9a3c3d7e7..0582c9ed5882 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -20,6 +20,7 @@ TypeVarExpr, Var, ) +from mypy.options import Options from mypy.server.subexpr import get_subexpressions from mypy.server.update import FineGrainedBuildManager from mypy.strconv import StrConv @@ -41,10 +42,10 @@ class ASTMergeSuite(DataSuite): def setup(self) -> None: super().setup() - self.str_conv = StrConv(show_ids=True) + self.str_conv = StrConv(show_ids=True, options=Options()) assert self.str_conv.id_mapper is not None self.id_mapper: IdMapper = self.str_conv.id_mapper - self.type_str_conv = TypeStrVisitor(self.id_mapper) + self.type_str_conv = TypeStrVisitor(self.id_mapper, options=Options()) def run_case(self, testcase: DataDrivenTestCase) -> None: name = testcase.name @@ -102,7 +103,11 @@ def build(self, source: str, testcase: DataDrivenTestCase) -> BuildResult | None options.export_types = True options.show_traceback = True options.allow_empty_bodies = True + options.force_uppercase_builtins = True main_path = os.path.join(test_temp_dir, "main") + + self.str_conv.options = options + self.type_str_conv.options = options with open(main_path, "w", encoding="utf8") as f: f.write(source) try: @@ -218,7 +223,12 @@ def dump_types( if type_map: a.append(f"## {module_id}") for expr in sorted( - type_map, key=lambda n: (n.line, short_type(n), str(n) + str(type_map[n])) + type_map, + key=lambda n: ( + n.line, + short_type(n), + n.str_with_options(self.str_conv.options) + str(type_map[n]), + ), ): typ = type_map[expr] a.append(f"{short_type(expr)}:{expr.line}: {self.format_type(typ)}") diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index 6a2d1e145251..3fc88277e01b 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -32,6 +32,7 @@ def test_parser(testcase: DataDrivenTestCase) -> None: The argument contains the description of the test case. """ options = Options() + options.force_uppercase_builtins = True options.hide_error_codes = True if testcase.file.endswith("python310.test"): @@ -47,7 +48,7 @@ def test_parser(testcase: DataDrivenTestCase) -> None: errors=None, options=options, ) - a = str(n).split("\n") + a = n.str_with_options(options).split("\n") except CompileError as e: a = e.messages assert_string_arrays_equal( diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 02dd11655382..62ba54591d9d 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -54,6 +54,7 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None "--no-error-summary", "--hide-error-codes", "--allow-empty-bodies", + "--force-uppercase-builtins", ] interpreter = python3_path mypy_cmdline.append(f"--python-version={'.'.join(map(str, PYTHON3_VERSION))}") diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index 3276f21540df..3455f41aa20a 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -46,6 +46,7 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti options.show_traceback = True options.python_version = PYTHON3_VERSION options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] + options.force_uppercase_builtins = True return options @@ -78,7 +79,7 @@ def test_semanal(testcase: DataDrivenTestCase) -> None: # output. for module in sorted(result.files.keys()): if module in testcase.test_modules: - a += str(result.files[module]).split("\n") + a += result.files[module].str_with_options(options).split("\n") except CompileError as e: a = e.messages if testcase.normalize_output: diff --git a/mypy/test/testtransform.py b/mypy/test/testtransform.py index c765bae12062..ba9fe8668fb4 100644 --- a/mypy/test/testtransform.py +++ b/mypy/test/testtransform.py @@ -40,6 +40,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None: options.semantic_analysis_only = True options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] options.show_traceback = True + options.force_uppercase_builtins = True result = build.build( sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir ) @@ -53,7 +54,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None: t = TypeAssertTransformVisitor() t.test_only = True file = t.mypyfile(result.files[module]) - a += str(file).split("\n") + a += file.str_with_options(options).split("\n") except CompileError as e: a = e.messages if testcase.normalize_output: diff --git a/mypy/test/testtypegen.py b/mypy/test/testtypegen.py index 3f09254f081a..4933bd3522a0 100644 --- a/mypy/test/testtypegen.py +++ b/mypy/test/testtypegen.py @@ -35,6 +35,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: options.export_types = True options.preserve_asts = True options.allow_empty_bodies = True + options.force_uppercase_builtins = True result = build.build( sources=[BuildSource("main", None, src)], options=options, @@ -66,8 +67,11 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: # Include node in output. keys.append(node) - for key in sorted(keys, key=lambda n: (n.line, short_type(n), str(n) + str(map[n]))): - ts = str(map[key]).replace("*", "") # Remove erased tags + for key in sorted( + keys, + key=lambda n: (n.line, short_type(n), str(n) + map[n].str_with_options(options)), + ): + ts = map[key].str_with_options(options).replace("*", "") # Remove erased tags ts = ts.replace("__main__.", "") a.append(f"{short_type(key)}({key.line}) : {ts}") except CompileError as e: diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 6fe65675554b..601cdf27466e 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -21,6 +21,7 @@ Expression, NameExpr, ) +from mypy.options import Options from mypy.plugins.common import find_shallow_matching_overload_item from mypy.state import state from mypy.subtypes import is_more_precise, is_proper_subtype, is_same_type, is_subtype @@ -124,10 +125,14 @@ def test_callable_type_with_var_args(self) -> None: assert_equal(str(c3), "def (X? =, *Y?) -> Any") def test_tuple_type(self) -> None: - assert_equal(str(TupleType([], self.fx.std_tuple)), "Tuple[]") - assert_equal(str(TupleType([self.x], self.fx.std_tuple)), "Tuple[X?]") + options = Options() + options.force_uppercase_builtins = True + assert_equal(TupleType([], self.fx.std_tuple).str_with_options(options), "Tuple[]") + assert_equal(TupleType([self.x], self.fx.std_tuple).str_with_options(options), "Tuple[X?]") assert_equal( - str(TupleType([self.x, AnyType(TypeOfAny.special_form)], self.fx.std_tuple)), + TupleType( + [self.x, AnyType(TypeOfAny.special_form)], self.fx.std_tuple + ).str_with_options(options), "Tuple[X?, Any]", ) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index f3329af6207a..fc387f417bdf 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -404,6 +404,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) self.fail, node.no_args, t, + self.options, unexpanded_type=t, disallow_any=disallow_any, ) @@ -419,7 +420,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) self.fail, self.note, disallow_any=disallow_any, - python_version=self.options.python_version, + options=self.options, use_generic_error=True, unexpanded_type=t, ) @@ -654,9 +655,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType: disallow_any = not self.is_typeshed_stub and self.options.disallow_any_generics - return get_omitted_any( - disallow_any, self.fail, self.note, typ, self.options.python_version, fullname - ) + return get_omitted_any(disallow_any, self.fail, self.note, typ, self.options, fullname) def analyze_type_with_type_info( self, info: TypeInfo, args: Sequence[Type], ctx: Context @@ -702,7 +701,7 @@ def analyze_type_with_type_info( self.fail, self.note, disallow_any=self.options.disallow_any_generics and not self.is_typeshed_stub, - python_version=self.options.python_version, + options=self.options, ) tup = info.tuple_type @@ -717,6 +716,7 @@ def analyze_type_with_type_info( self.fail, False, ctx, + self.options, use_standard_error=True, ) return tup.copy_modified(items=self.anal_array(tup.items), fallback=instance) @@ -732,6 +732,7 @@ def analyze_type_with_type_info( self.fail, False, ctx, + self.options, use_standard_error=True, ) # Create a named TypedDictType @@ -1600,12 +1601,12 @@ def get_omitted_any( fail: MsgCallback, note: MsgCallback, orig_type: Type, - python_version: tuple[int, int], + options: Options, fullname: str | None = None, unexpanded_type: Type | None = None, ) -> AnyType: if disallow_any: - nongen_builtins = get_nongen_builtins(python_version) + nongen_builtins = get_nongen_builtins(options.python_version) if fullname in nongen_builtins: typ = orig_type # We use a dedicated error message for builtin generics (as the most common case). @@ -1617,7 +1618,7 @@ def get_omitted_any( ) else: typ = unexpanded_type or orig_type - type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ) + type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ, options) fail( message_registry.BARE_GENERIC.format(quote_type_string(type_str)), @@ -1630,7 +1631,10 @@ def get_omitted_any( ) # Ideally, we'd check whether the type is quoted or `from __future__ annotations` # is set before issuing this note - if python_version < (3, 9) and base_fullname in GENERIC_STUB_NOT_AT_RUNTIME_TYPES: + if ( + options.python_version < (3, 9) + and base_fullname in GENERIC_STUB_NOT_AT_RUNTIME_TYPES + ): # Recommend `from __future__ import annotations` or to put type in quotes # (string literal escaping) for classes not generic at runtime note( @@ -1654,7 +1658,7 @@ def fix_instance( fail: MsgCallback, note: MsgCallback, disallow_any: bool, - python_version: tuple[int, int], + options: Options, use_generic_error: bool = False, unexpanded_type: Type | None = None, ) -> None: @@ -1667,9 +1671,7 @@ def fix_instance( fullname: str | None = None else: fullname = t.type.fullname - any_type = get_omitted_any( - disallow_any, fail, note, t, python_version, fullname, unexpanded_type - ) + any_type = get_omitted_any(disallow_any, fail, note, t, options, fullname, unexpanded_type) t.args = (any_type,) * len(t.type.type_vars) return # Invalid number of type parameters. @@ -1691,6 +1693,7 @@ def expand_type_alias( fail: MsgCallback, no_args: bool, ctx: Context, + options: Options, *, unexpanded_type: Type | None = None, disallow_any: bool = False, @@ -1714,6 +1717,7 @@ def expand_type_alias( node, ctx.line, ctx.column, + options, disallow_any=disallow_any, fail=fail, unexpanded_type=unexpanded_type, @@ -1743,7 +1747,7 @@ def expand_type_alias( else: msg = f"Bad number of arguments for type alias, expected: {exp_len}, given: {act_len}" fail(msg, ctx, code=codes.TYPE_ARG) - return set_any_tvars(node, ctx.line, ctx.column, from_error=True) + return set_any_tvars(node, ctx.line, ctx.column, options, from_error=True) # TODO: we need to check args validity w.r.t alias.alias_tvars. # Otherwise invalid instantiations will be allowed in runtime context. # Note: in type context, these will be still caught by semanal_typeargs. @@ -1764,6 +1768,7 @@ def set_any_tvars( node: TypeAlias, newline: int, newcolumn: int, + options: Options, *, from_error: bool = False, disallow_any: bool = False, @@ -1780,7 +1785,7 @@ def set_any_tvars( type_str = ( unexpanded_type.name if isinstance(unexpanded_type, UnboundType) - else format_type_bare(unexpanded_type) + else format_type_bare(unexpanded_type, options) ) else: type_str = node.name @@ -2023,24 +2028,20 @@ def make_optional_type(t: Type) -> Type: return UnionType([t, NoneType()], t.line, t.column) -def fix_instance_types( - t: Type, fail: MsgCallback, note: MsgCallback, python_version: tuple[int, int] -) -> None: +def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, options: Options) -> None: """Recursively fix all instance types (type argument count) in a given type. For example 'Union[Dict, List[str, int]]' will be transformed into 'Union[Dict[Any, Any], List[Any]]' in place. """ - t.accept(InstanceFixer(fail, note, python_version)) + t.accept(InstanceFixer(fail, note, options)) class InstanceFixer(TypeTraverserVisitor): - def __init__( - self, fail: MsgCallback, note: MsgCallback, python_version: tuple[int, int] - ) -> None: + def __init__(self, fail: MsgCallback, note: MsgCallback, options: Options) -> None: self.fail = fail self.note = note - self.python_version = python_version + self.options = options def visit_instance(self, typ: Instance) -> None: super().visit_instance(typ) @@ -2050,7 +2051,7 @@ def visit_instance(self, typ: Instance) -> None: self.fail, self.note, disallow_any=False, - python_version=self.python_version, + options=self.options, use_generic_error=True, ) diff --git a/mypy/types.py b/mypy/types.py index e78209be058f..0af15f5759a4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -33,6 +33,7 @@ FuncItem, SymbolNode, ) +from mypy.options import Options from mypy.state import state from mypy.util import IdMapper @@ -256,7 +257,10 @@ def accept(self, visitor: TypeVisitor[T]) -> T: raise RuntimeError("Not implemented") def __repr__(self) -> str: - return self.accept(TypeStrVisitor()) + return self.accept(TypeStrVisitor(options=Options())) + + def str_with_options(self, options: Options) -> str: + return self.accept(TypeStrVisitor(options=options)) def serialize(self) -> JsonDict | str: raise NotImplementedError(f"Cannot serialize {self.__class__.__name__} instance") @@ -2944,9 +2948,10 @@ class TypeStrVisitor(SyntheticTypeVisitor[str]): - Represent the NoneType type as None. """ - def __init__(self, id_mapper: IdMapper | None = None) -> None: + def __init__(self, id_mapper: IdMapper | None = None, *, options: Options) -> None: self.id_mapper = id_mapper self.any_as_dots = False + self.options = options def visit_unbound_type(self, t: UnboundType) -> str: s = t.name + "?" @@ -2988,7 +2993,7 @@ def visit_instance(self, t: Instance) -> str: if t.last_known_value and not t.args: # Instances with a literal fallback should never be generic. If they are, # something went wrong so we fall back to showing the full Instance repr. - s = f"{t.last_known_value}?" + s = f"{t.last_known_value.accept(self)}?" else: s = t.type.fullname or t.type.name or "" @@ -3136,11 +3141,12 @@ def visit_overloaded(self, t: Overloaded) -> str: def visit_tuple_type(self, t: TupleType) -> str: s = self.list_str(t.items) + tuple_name = "tuple" if self.options.use_lowercase_names() else "Tuple" if t.partial_fallback and t.partial_fallback.type: fallback_name = t.partial_fallback.type.fullname if fallback_name != "builtins.tuple": - return f"Tuple[{s}, fallback={t.partial_fallback.accept(self)}]" - return f"Tuple[{s}]" + return f"{tuple_name}[{s}, fallback={t.partial_fallback.accept(self)}]" + return f"{tuple_name}[{s}]" def visit_typeddict_type(self, t: TypedDictType) -> str: def item_str(name: str, typ: str) -> str: diff --git a/mypyc/build.py b/mypyc/build.py index 8e1ee8078c11..5fc041e2dcf2 100644 --- a/mypyc/build.py +++ b/mypyc/build.py @@ -225,7 +225,7 @@ def generate_c( if compiler_options.verbose: print(f"Parsed and typechecked in {t1 - t0:.3f}s") - errors = Errors() + errors = Errors(options) modules, ctext = emitmodule.compile_modules_to_c( result, compiler_options=compiler_options, errors=errors, groups=groups ) diff --git a/mypyc/errors.py b/mypyc/errors.py index 1dd269fe25f3..8bc9b2714f75 100644 --- a/mypyc/errors.py +++ b/mypyc/errors.py @@ -1,13 +1,14 @@ from __future__ import annotations import mypy.errors +from mypy.options import Options class Errors: - def __init__(self) -> None: + def __init__(self, options: Options) -> None: self.num_errors = 0 self.num_warnings = 0 - self._errors = mypy.errors.Errors(hide_error_codes=True) + self._errors = mypy.errors.Errors(options, hide_error_codes=True) def error(self, msg: str, path: str, line: int) -> None: self._errors.report(line, None, msg, severity="error", file=path) diff --git a/mypyc/irbuild/format_str_tokenizer.py b/mypyc/irbuild/format_str_tokenizer.py index 5ab38d0f2264..480c683aa164 100644 --- a/mypyc/irbuild/format_str_tokenizer.py +++ b/mypyc/irbuild/format_str_tokenizer.py @@ -13,6 +13,7 @@ from mypy.errors import Errors from mypy.messages import MessageBuilder from mypy.nodes import Context, Expression +from mypy.options import Options from mypyc.ir.ops import Integer, Value from mypyc.ir.rtypes import ( c_pyssize_t_rprimitive, @@ -108,7 +109,9 @@ def tokenizer_format_call(format_str: str) -> tuple[list[str], list[FormatOp]] | """ # Creates an empty MessageBuilder here. # It wouldn't be used since the code has passed the type-checking. - specifiers = parse_format_value(format_str, EMPTY_CONTEXT, MessageBuilder(Errors(), {})) + specifiers = parse_format_value( + format_str, EMPTY_CONTEXT, MessageBuilder(Errors(Options()), {}) + ) if specifiers is None: return None format_ops = generate_format_ops(specifiers) diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 8d6dd90d770d..dc054ac9002f 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -241,7 +241,7 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> groups=groups, alt_lib_path=".", ) - errors = Errors() + errors = Errors(options) ir, cfiles = emitmodule.compile_modules_to_c( result, compiler_options=compiler_options, errors=errors, groups=groups ) diff --git a/mypyc/test/testutil.py b/mypyc/test/testutil.py index 609ffc27385e..796811a6363c 100644 --- a/mypyc/test/testutil.py +++ b/mypyc/test/testutil.py @@ -121,7 +121,7 @@ def build_ir_for_single_file2( if result.errors: raise CompileError(result.errors) - errors = Errors() + errors = Errors(options) modules = build_ir( [result.files["__main__"]], result.graph, diff --git a/test-data/unit/check-lowercase.test b/test-data/unit/check-lowercase.test new file mode 100644 index 000000000000..93b180ed88d9 --- /dev/null +++ b/test-data/unit/check-lowercase.test @@ -0,0 +1,44 @@ + +[case testTupleLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = (3,) +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Tuple[int]") +[builtins fixtures/tuple.pyi] + +[case testTupleLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = (3,) +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "tuple[int]") +[builtins fixtures/tuple.pyi] + +[case testListLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = [3] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "List[int]") + +[case testListLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = [3] +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "list[int]") + +[case testDictLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = {"key": "value"} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Dict[str, str]") + +[case testDictLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = {"key": "value"} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "dict[str, str]") + +[case testSetLowercaseSettingOff] +# flags: --python-version 3.9 --force-uppercase-builtins +x = {3} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Set[int]") +[builtins fixtures/set.pyi] + +[case testSetLowercaseSettingOn] +# flags: --python-version 3.9 --no-force-uppercase-builtins +x = {3} +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "set[int]") +[builtins fixtures/set.pyi] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index d598fe13b7e9..92b9f7f04f26 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -762,9 +762,9 @@ class Person(TypedDict): name: str age: int -def foo(x: Unpack[Person]) -> None: # E: TypedDict('__main__.Person', {'name': builtins.str, 'age': builtins.int}) cannot be unpacked (must be tuple or TypeVarTuple) +def foo(x: Unpack[Person]) -> None: # E: "Person" cannot be unpacked (must be tuple or TypeVarTuple) ... -def bar(x: int, *args: Unpack[Person]) -> None: # E: TypedDict('__main__.Person', {'name': builtins.str, 'age': builtins.int}) cannot be unpacked (must be tuple or TypeVarTuple) +def bar(x: int, *args: Unpack[Person]) -> None: # E: "Person" cannot be unpacked (must be tuple or TypeVarTuple) ... def baz(**kwargs: Unpack[Person]) -> None: # OK ... diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index a4ed905dcb9f..d09ed87d3afc 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1453,7 +1453,7 @@ from typing import Tuple heterogenous_tuple: Tuple[Unpack[Tuple[int, str]]] homogenous_tuple: Tuple[Unpack[Tuple[int, ...]]] -bad: Tuple[Unpack[int]] # E: builtins.int cannot be unpacked (must be tuple or TypeVarTuple) +bad: Tuple[Unpack[int]] # E: "int" cannot be unpacked (must be tuple or TypeVarTuple) [builtins fixtures/tuple.pyi] [case testTypeVarTuple]