From b62aeb8cc2ec17d11e5bcedd4d0d90d7fadb0e96 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 2 Oct 2022 16:34:25 +0100 Subject: [PATCH 1/7] Allow enabling individual experimental features --- mypy/config_parser.py | 2 ++ mypy/main.py | 15 +++++++++++---- mypy/options.py | 6 +++++- mypy/semanal.py | 15 ++++++++++++--- mypy/test/testcheck.py | 4 +++- mypy/test/testfinegrained.py | 4 ++-- mypy/test/testsemanal.py | 4 ++-- mypy/test/testtransform.py | 3 ++- mypy/typeanal.py | 4 ---- mypyc/test-data/run-functions.test | 2 +- mypyc/test/test_run.py | 2 -- test-data/unit/check-flags.test | 11 +++++++++++ test-data/unit/fine-grained.test | 1 - test-data/unit/lib-stub/typing_extensions.pyi | 3 +++ 14 files changed, 54 insertions(+), 22 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 52f8182220c1..100d74e8f956 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -154,6 +154,7 @@ def check_follow_imports(choice: str) -> str: "plugins": lambda s: [p.strip() for p in s.split(",")], "always_true": lambda s: [p.strip() for p in s.split(",")], "always_false": lambda s: [p.strip() for p in s.split(",")], + "enable_incomplete_feature": lambda s: [p.strip() for p in s.split(",")], "disable_error_code": lambda s: validate_codes([p.strip() for p in s.split(",")]), "enable_error_code": lambda s: validate_codes([p.strip() for p in s.split(",")]), "package_root": lambda s: [p.strip() for p in s.split(",")], @@ -174,6 +175,7 @@ def check_follow_imports(choice: str) -> str: "plugins": try_split, "always_true": try_split, "always_false": try_split, + "enable_incomplete_feature": try_split, "disable_error_code": lambda s: validate_codes(try_split(s)), "enable_error_code": lambda s: validate_codes(try_split(s)), "package_root": try_split, diff --git a/mypy/main.py b/mypy/main.py index 1074a9ac70d8..0c8da254d73b 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -18,7 +18,7 @@ from mypy.find_sources import InvalidSourceList, create_source_list from mypy.fscache import FileSystemCache from mypy.modulefinder import BuildSource, FindModuleCache, SearchPaths, get_search_dirs, mypy_path -from mypy.options import BuildType, Options +from mypy.options import INCOMPLETE_FEATURES, BuildType, Options from mypy.split_namespace import SplitNamespace from mypy.version import __version__ @@ -979,6 +979,11 @@ def add_invertible_flag( action="store_true", help="Disable experimental support for recursive type aliases", ) + parser.add_argument( + "--enable-incomplete-feature", + action="append", + help="Enable support of incomplete/experimental features for early preview", + ) internals_group.add_argument( "--custom-typeshed-dir", metavar="DIR", help="Use the custom typeshed in DIR" ) @@ -1107,9 +1112,6 @@ def add_invertible_flag( parser.add_argument( "--cache-map", nargs="+", dest="special-opts:cache_map", help=argparse.SUPPRESS ) - parser.add_argument( - "--enable-incomplete-features", action="store_true", help=argparse.SUPPRESS - ) # options specifying code to check code_group = parser.add_argument_group( @@ -1269,6 +1271,11 @@ def set_strict_flags() -> None: # Enabling an error code always overrides disabling options.disabled_error_codes -= options.enabled_error_codes + # Validate incomplete features. + for feature in options.enable_incomplete_feature: + if feature not in INCOMPLETE_FEATURES: + parser.error(f"Unknown incomplete feature: {feature}") + # Compute absolute path for custom typeshed (if present). if options.custom_typeshed_dir is not None: options.abs_custom_typeshed_dir = os.path.abspath(options.custom_typeshed_dir) diff --git a/mypy/options.py b/mypy/options.py index 76df064842f2..290537a2d16a 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -60,6 +60,10 @@ class BuildType: "debug_cache" } +# Features that are currently incomplete/experimental +TYPE_VAR_TUPLE = "TypeVarTuple" +INCOMPLETE_FEATURES = {TYPE_VAR_TUPLE} + class Options: """Options collected from flags.""" @@ -262,7 +266,7 @@ def __init__(self) -> None: self.dump_type_stats = False self.dump_inference_stats = False self.dump_build_stats = False - self.enable_incomplete_features = False + self.enable_incomplete_feature: list[str] = [] self.timing_stats: str | None = None # -- test options -- diff --git a/mypy/semanal.py b/mypy/semanal.py index 5a1787c50650..85833b4870d8 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -178,7 +178,7 @@ type_aliases_source_versions, typing_extensions_aliases, ) -from mypy.options import Options +from mypy.options import TYPE_VAR_TUPLE, Options from mypy.patterns import ( AsPattern, ClassPattern, @@ -3911,8 +3911,7 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool: if len(call.args) > 1: self.fail("Only the first argument to TypeVarTuple has defined semantics", s) - if not self.options.enable_incomplete_features: - self.fail('"TypeVarTuple" is not supported by mypy yet', s) + if not self.incomplete_feature_enabled(TYPE_VAR_TUPLE, s): return False name = self.extract_typevarlike_name(s, call) @@ -5972,6 +5971,16 @@ def note(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None: return self.errors.report(ctx.get_line(), ctx.get_column(), msg, severity="note", code=code) + def incomplete_feature_enabled(self, feature: str, ctx: Context) -> bool: + if feature not in self.options.enable_incomplete_feature: + self.fail( + f'"{feature}" support is experimental,' + f" use --enable-incomplete-feature={feature} to enable", + ctx, + ) + return False + return True + def accept(self, node: Node) -> None: try: node.accept(self) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 8f0fe85d704e..75ba5e2cce1e 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -10,6 +10,7 @@ from mypy.build import Graph from mypy.errors import CompileError from mypy.modulefinder import BuildSource, FindModuleCache, SearchPaths +from mypy.options import TYPE_VAR_TUPLE from mypy.semanal_main import core_modules from mypy.test.config import test_data_prefix, test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite, FileOperation, module_from_path @@ -110,7 +111,8 @@ def run_case_once( # Parse options after moving files (in case mypy.ini is being moved). options = parse_options(original_program_text, testcase, incremental_step) options.use_builtins_fixtures = True - options.enable_incomplete_features = True + if not testcase.name.endswith("_no_incomplete"): + options.enable_incomplete_feature = [TYPE_VAR_TUPLE] options.show_traceback = True # Enable some options automatically based on test file name. diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index 1fc73146e749..b7c522454fb1 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -29,7 +29,7 @@ from mypy.errors import CompileError from mypy.find_sources import create_source_list from mypy.modulefinder import BuildSource -from mypy.options import Options +from mypy.options import TYPE_VAR_TUPLE, Options from mypy.server.mergecheck import check_consistency from mypy.server.update import sort_messages_preserving_file_order from mypy.test.config import test_temp_dir @@ -153,7 +153,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo options.use_fine_grained_cache = self.use_cache and not build_cache options.cache_fine_grained = self.use_cache options.local_partial_types = True - options.enable_incomplete_features = True + options.enable_incomplete_feature = [TYPE_VAR_TUPLE] # Treat empty bodies safely for these test cases. options.allow_empty_bodies = not testcase.name.endswith("_no_empty") if re.search("flags:.*--follow-imports", source) is None: diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index 70b96c9781fc..fce1e9ed0e9c 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -11,7 +11,7 @@ from mypy.errors import CompileError from mypy.modulefinder import BuildSource from mypy.nodes import TypeInfo -from mypy.options import Options +from mypy.options import TYPE_VAR_TUPLE, Options from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import ( @@ -46,7 +46,7 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti options.semantic_analysis_only = True options.show_traceback = True options.python_version = PYTHON3_VERSION - options.enable_incomplete_features = True + options.enable_incomplete_feature = [TYPE_VAR_TUPLE] return options diff --git a/mypy/test/testtransform.py b/mypy/test/testtransform.py index 179b2f528b1e..cc8f486e39b8 100644 --- a/mypy/test/testtransform.py +++ b/mypy/test/testtransform.py @@ -7,6 +7,7 @@ from mypy import build from mypy.errors import CompileError from mypy.modulefinder import BuildSource +from mypy.options import TYPE_VAR_TUPLE from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import assert_string_arrays_equal, normalize_error_messages, parse_options @@ -39,7 +40,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None: options = parse_options(src, testcase, 1) options.use_builtins_fixtures = True options.semantic_analysis_only = True - options.enable_incomplete_features = True + options.enable_incomplete_feature = [TYPE_VAR_TUPLE] options.show_traceback = True result = build.build( sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 2ed9523c410d..c58a17c0c204 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -569,10 +569,6 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ # In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args) return self.named_type("builtins.bool") elif fullname in ("typing.Unpack", "typing_extensions.Unpack"): - # We don't want people to try to use this yet. - if not self.options.enable_incomplete_features: - self.fail('"Unpack" is not supported yet, use --enable-incomplete-features', t) - return AnyType(TypeOfAny.from_error) return UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column) return None diff --git a/mypyc/test-data/run-functions.test b/mypyc/test-data/run-functions.test index 28ff36341b41..21993891c4e3 100644 --- a/mypyc/test-data/run-functions.test +++ b/mypyc/test-data/run-functions.test @@ -1242,7 +1242,7 @@ def g() -> None: g() -[case testIncompleteFeatureUnpackKwargsCompiled] +[case testUnpackKwargsCompiled] from typing_extensions import Unpack, TypedDict class Person(TypedDict): diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 63e4f153da40..7e5992511704 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -194,8 +194,6 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> options.preserve_asts = True options.allow_empty_bodies = True options.incremental = self.separate - if "IncompleteFeature" in testcase.name: - options.enable_incomplete_features = True # Avoid checking modules/packages named 'unchecked', to provide a way # to test interacting with code we don't have types for. diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 1c58b0ebb8bd..d7ce4c8848e3 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -2117,3 +2117,14 @@ x: int = "" # E: Incompatible types in assignment (expression has type "str", v [case testHideErrorCodes] # flags: --hide-error-codes x: int = "" # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testTypeVarTupleDisabled_no_incomplete] +from typing_extensions import TypeVarTuple +Ts = TypeVarTuple("Ts") # E: "TypeVarTuple" support is experimental, use --enable-incomplete-feature=TypeVarTuple to enable +[builtins fixtures/tuple.pyi] + +[case testTypeVarTupleEnabled_no_incomplete] +# flags: --enable-incomplete-feature=TypeVarTuple +from typing_extensions import TypeVarTuple +Ts = TypeVarTuple("Ts") # OK +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 49f03a23177e..05361924ed89 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -9901,7 +9901,6 @@ x = 0 # Arbitrary change to trigger reprocessing a.py:3: note: Revealed type is "Tuple[Literal[1]?, Literal['x']?]" [case testUnpackKwargsUpdateFine] -# flags: --enable-incomplete-features import m [file shared.py] from typing_extensions import TypedDict diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index b82b73d49a71..e92f7e913502 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -10,6 +10,9 @@ class _SpecialForm: def __getitem__(self, typeargs: Any) -> Any: pass + def __call__(self, arg: Any) -> Any: + pass + NamedTuple = 0 Protocol: _SpecialForm = ... def runtime_checkable(x: _T) -> _T: pass From 45675a037c544931dfb8823446d71a04be3611c3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 2 Oct 2022 17:09:31 +0100 Subject: [PATCH 2/7] Remove outdated test --- test-data/unit/cmdline.test | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 168cf0a8d738..495a24d434ce 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -1419,7 +1419,6 @@ exclude = (?x)( [out] c/cpkg.py:1: error: "int" not callable - [case testCmdlineTimingStats] # cmd: mypy --timing-stats timing.txt . [file b/__init__.py] @@ -1431,11 +1430,6 @@ b \d+ b\.c \d+ .* -[case testCmdlineEnableIncompleteFeatures] -# cmd: mypy --enable-incomplete-features a.py -[file a.py] -pass - [case testShadowTypingModuleEarlyLoad] # cmd: mypy dir [file dir/__init__.py] From f9c7e7970f97f4fb8d44d7ec58092757e73c3452 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 2 Oct 2022 17:29:31 +0100 Subject: [PATCH 3/7] Avoid crashing --- mypy/semanal_shared.py | 4 ++++ mypy/typeanal.py | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index d9ded032591b..63f4f5516f79 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -82,6 +82,10 @@ def fail( def note(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None: raise NotImplementedError + @abstractmethod + def incomplete_feature_enabled(self, feature: str, ctx: Context) -> bool: + raise NotImplementedError + @abstractmethod def record_incomplete_ref(self) -> None: raise NotImplementedError diff --git a/mypy/typeanal.py b/mypy/typeanal.py index c58a17c0c204..35790ddeb275 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -38,7 +38,7 @@ check_arg_names, get_nongen_builtins, ) -from mypy.options import Options +from mypy.options import Options, TYPE_VAR_TUPLE from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs from mypy.tvar_scope import TypeVarLikeScope @@ -569,7 +569,14 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ # In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args) return self.named_type("builtins.bool") elif fullname in ("typing.Unpack", "typing_extensions.Unpack"): - return UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column) + inner = self.anal_type(t.args[0]) + if ( + isinstance(inner, TypeVarTupleType) + and not self.api.incomplete_feature_enabled(TYPE_VAR_TUPLE, t) + or isinstance(inner, UnboundType) + ): + return AnyType(TypeOfAny.from_error) + return UnpackType(inner, line=t.line, column=t.column) return None def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType: From 071afb4ca93512a1f15a0d3f9cd86377c91fc2aa Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 2 Oct 2022 17:32:03 +0100 Subject: [PATCH 4/7] Fix black --- mypy/typeanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 35790ddeb275..da9a2cd3bf7c 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -38,7 +38,7 @@ check_arg_names, get_nongen_builtins, ) -from mypy.options import Options, TYPE_VAR_TUPLE +from mypy.options import TYPE_VAR_TUPLE, Options from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs from mypy.tvar_scope import TypeVarLikeScope From 1153face83a48848f5423ec43ce77e393b26d06a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 5 Oct 2022 23:25:05 +0100 Subject: [PATCH 5/7] Update mypy/options.py Co-authored-by: Nikita Sobolev --- mypy/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/options.py b/mypy/options.py index 290537a2d16a..a8cf4a253ffe 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -61,8 +61,8 @@ class BuildType: } # Features that are currently incomplete/experimental -TYPE_VAR_TUPLE = "TypeVarTuple" -INCOMPLETE_FEATURES = {TYPE_VAR_TUPLE} +TYPE_VAR_TUPLE: Final = "TypeVarTuple" +INCOMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE,)) class Options: From 32d678b89af3f9bb169d1727066ecfc2c5a57441 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2022 20:37:47 +0100 Subject: [PATCH 6/7] Keep Unpack incomplete; also put back --enable-incomplete-features as deprecated --- mypy/main.py | 11 +++++++++++ mypy/options.py | 4 +++- mypy/test/testcheck.py | 4 ++-- mypy/test/testfinegrained.py | 4 ++-- mypy/test/testsemanal.py | 4 ++-- mypy/test/testtransform.py | 4 ++-- mypy/typeanal.py | 11 +++-------- mypyc/test/test_run.py | 3 ++- test-data/unit/cmdline.test | 8 ++++++++ test-data/unit/lib-stub/typing_extensions.pyi | 3 --- 10 files changed, 35 insertions(+), 21 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 0c8da254d73b..fd9c3189de5f 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -982,6 +982,7 @@ def add_invertible_flag( parser.add_argument( "--enable-incomplete-feature", action="append", + metavar="FEATURE", help="Enable support of incomplete/experimental features for early preview", ) internals_group.add_argument( @@ -1112,6 +1113,10 @@ def add_invertible_flag( parser.add_argument( "--cache-map", nargs="+", dest="special-opts:cache_map", help=argparse.SUPPRESS ) + # This one is deprecated, but we will keep it for few releases. + parser.add_argument( + "--enable-incomplete-features", action="store_true", help=argparse.SUPPRESS + ) # options specifying code to check code_group = parser.add_argument_group( @@ -1275,6 +1280,12 @@ def set_strict_flags() -> None: for feature in options.enable_incomplete_feature: if feature not in INCOMPLETE_FEATURES: parser.error(f"Unknown incomplete feature: {feature}") + if options.enable_incomplete_features: + print( + "Warning: --enable-incomplete-features is deprecated, use" + " --enable-incomplete-feature=FEATURE instead" + ) + options.enable_incomplete_feature = list(INCOMPLETE_FEATURES) # Compute absolute path for custom typeshed (if present). if options.custom_typeshed_dir is not None: diff --git a/mypy/options.py b/mypy/options.py index a8cf4a253ffe..221df27bb2ce 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -62,7 +62,8 @@ class BuildType: # Features that are currently incomplete/experimental TYPE_VAR_TUPLE: Final = "TypeVarTuple" -INCOMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE,)) +UNPACK: Final = "Unpack" +INCOMPLETE_FEATURES: Final = frozenset((TYPE_VAR_TUPLE, UNPACK)) class Options: @@ -266,6 +267,7 @@ def __init__(self) -> None: self.dump_type_stats = False self.dump_inference_stats = False self.dump_build_stats = False + self.enable_incomplete_features = False # deprecated self.enable_incomplete_feature: list[str] = [] self.timing_stats: str | None = None diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 75ba5e2cce1e..442e25b54ff2 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -10,7 +10,7 @@ from mypy.build import Graph from mypy.errors import CompileError from mypy.modulefinder import BuildSource, FindModuleCache, SearchPaths -from mypy.options import TYPE_VAR_TUPLE +from mypy.options import TYPE_VAR_TUPLE, UNPACK from mypy.semanal_main import core_modules from mypy.test.config import test_data_prefix, test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite, FileOperation, module_from_path @@ -112,7 +112,7 @@ def run_case_once( options = parse_options(original_program_text, testcase, incremental_step) options.use_builtins_fixtures = True if not testcase.name.endswith("_no_incomplete"): - options.enable_incomplete_feature = [TYPE_VAR_TUPLE] + options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] options.show_traceback = True # Enable some options automatically based on test file name. diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index b7c522454fb1..b19c49bf60bc 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -29,7 +29,7 @@ from mypy.errors import CompileError from mypy.find_sources import create_source_list from mypy.modulefinder import BuildSource -from mypy.options import TYPE_VAR_TUPLE, Options +from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options from mypy.server.mergecheck import check_consistency from mypy.server.update import sort_messages_preserving_file_order from mypy.test.config import test_temp_dir @@ -153,7 +153,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo options.use_fine_grained_cache = self.use_cache and not build_cache options.cache_fine_grained = self.use_cache options.local_partial_types = True - options.enable_incomplete_feature = [TYPE_VAR_TUPLE] + options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] # Treat empty bodies safely for these test cases. options.allow_empty_bodies = not testcase.name.endswith("_no_empty") if re.search("flags:.*--follow-imports", source) is None: diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index fce1e9ed0e9c..6cfd53f09beb 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -11,7 +11,7 @@ from mypy.errors import CompileError from mypy.modulefinder import BuildSource from mypy.nodes import TypeInfo -from mypy.options import TYPE_VAR_TUPLE, Options +from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import ( @@ -46,7 +46,7 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti options.semantic_analysis_only = True options.show_traceback = True options.python_version = PYTHON3_VERSION - options.enable_incomplete_feature = [TYPE_VAR_TUPLE] + options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] return options diff --git a/mypy/test/testtransform.py b/mypy/test/testtransform.py index cc8f486e39b8..1d3d4468444e 100644 --- a/mypy/test/testtransform.py +++ b/mypy/test/testtransform.py @@ -7,7 +7,7 @@ from mypy import build from mypy.errors import CompileError from mypy.modulefinder import BuildSource -from mypy.options import TYPE_VAR_TUPLE +from mypy.options import TYPE_VAR_TUPLE, UNPACK from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase, DataSuite from mypy.test.helpers import assert_string_arrays_equal, normalize_error_messages, parse_options @@ -40,7 +40,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None: options = parse_options(src, testcase, 1) options.use_builtins_fixtures = True options.semantic_analysis_only = True - options.enable_incomplete_feature = [TYPE_VAR_TUPLE] + options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] options.show_traceback = True result = build.build( sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir diff --git a/mypy/typeanal.py b/mypy/typeanal.py index da9a2cd3bf7c..add18deb34a2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -38,7 +38,7 @@ check_arg_names, get_nongen_builtins, ) -from mypy.options import TYPE_VAR_TUPLE, Options +from mypy.options import UNPACK, Options from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs from mypy.tvar_scope import TypeVarLikeScope @@ -569,14 +569,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ # In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args) return self.named_type("builtins.bool") elif fullname in ("typing.Unpack", "typing_extensions.Unpack"): - inner = self.anal_type(t.args[0]) - if ( - isinstance(inner, TypeVarTupleType) - and not self.api.incomplete_feature_enabled(TYPE_VAR_TUPLE, t) - or isinstance(inner, UnboundType) - ): + if not self.api.incomplete_feature_enabled(UNPACK, t): return AnyType(TypeOfAny.from_error) - return UnpackType(inner, line=t.line, column=t.column) + return UnpackType(self.anal_type(t.args[0]), line=t.line, column=t.column) return None def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType: diff --git a/mypyc/test/test_run.py b/mypyc/test/test_run.py index 7e5992511704..351caf7c93ed 100644 --- a/mypyc/test/test_run.py +++ b/mypyc/test/test_run.py @@ -15,7 +15,7 @@ from mypy import build from mypy.errors import CompileError -from mypy.options import Options +from mypy.options import TYPE_VAR_TUPLE, UNPACK, Options from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase from mypy.test.helpers import assert_module_equivalence, perform_file_operations @@ -194,6 +194,7 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) -> options.preserve_asts = True options.allow_empty_bodies = True options.incremental = self.separate + options.enable_incomplete_feature = [TYPE_VAR_TUPLE, UNPACK] # Avoid checking modules/packages named 'unchecked', to provide a way # to test interacting with code we don't have types for. diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 495a24d434ce..36d48dc2252e 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -1430,6 +1430,14 @@ b \d+ b\.c \d+ .* +[case testCmdlineEnableIncompleteFeatures] +# cmd: mypy --enable-incomplete-features a.py +[file a.py] +pass +[out] +Warning: --enable-incomplete-features is deprecated, use --enable-incomplete-feature=FEATURE instead +== Return code: 0 + [case testShadowTypingModuleEarlyLoad] # cmd: mypy dir [file dir/__init__.py] diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index e92f7e913502..b82b73d49a71 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -10,9 +10,6 @@ class _SpecialForm: def __getitem__(self, typeargs: Any) -> Any: pass - def __call__(self, arg: Any) -> Any: - pass - NamedTuple = 0 Protocol: _SpecialForm = ... def runtime_checkable(x: _T) -> _T: pass From 09167c0fc7d84d571f4e307506aaeeeb4e1920ef Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Oct 2022 20:43:50 +0100 Subject: [PATCH 7/7] Keep the lib-stub tweak --- test-data/unit/lib-stub/typing_extensions.pyi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index b82b73d49a71..e92f7e913502 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -10,6 +10,9 @@ class _SpecialForm: def __getitem__(self, typeargs: Any) -> Any: pass + def __call__(self, arg: Any) -> Any: + pass + NamedTuple = 0 Protocol: _SpecialForm = ... def runtime_checkable(x: _T) -> _T: pass