diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 474e9a46818d..c47f9a9fda6c 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -125,6 +125,14 @@ auto HandleParseNode(Context& context, Parse::DefaultSelfImplAsId node_id) return true; } +static auto DiagnoseExtendImplOutsideClass(Context& context, + Parse::AnyImplDeclId node_id) + -> void { + CARBON_DIAGNOSTIC(ExtendImplOutsideClass, Error, + "`extend impl` can only be used in a class"); + context.emitter().Emit(node_id, ExtendImplOutsideClass); +} + // Process an `extend impl` declaration by extending the impl scope with the // `impl`'s scope. static auto ExtendImpl(Context& context, Parse::NodeId extend_node, @@ -134,13 +142,15 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node, SemIR::InstId constraint_inst_id, SemIR::TypeId constraint_id) -> void { auto parent_scope_id = context.decl_name_stack().PeekParentScopeId(); + if (!parent_scope_id.has_value()) { + DiagnoseExtendImplOutsideClass(context, node_id); + return; + } auto& parent_scope = context.name_scopes().Get(parent_scope_id); // TODO: This is also valid in a mixin. if (!TryAsClassScope(context, parent_scope_id)) { - CARBON_DIAGNOSTIC(ExtendImplOutsideClass, Error, - "`extend impl` can only be used in a class"); - context.emitter().Emit(node_id, ExtendImplOutsideClass); + DiagnoseExtendImplOutsideClass(context, node_id); return; } diff --git a/toolchain/check/testdata/impl/fail_extend_impl_scope.carbon b/toolchain/check/testdata/impl/fail_extend_impl_scope.carbon deleted file mode 100644 index 185b8f711ce6..000000000000 --- a/toolchain/check/testdata/impl/fail_extend_impl_scope.carbon +++ /dev/null @@ -1,64 +0,0 @@ -// Part of the Carbon Language project, under the Apache License v2.0 with LLVM -// Exceptions. See /LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -// AUTOUPDATE -// TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/fail_extend_impl_scope.carbon -// TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/fail_extend_impl_scope.carbon - -interface I {} - -// CHECK:STDERR: fail_extend_impl_scope.carbon:[[@LINE+4]]:1: error: `extend impl` can only be used in a class [ExtendImplOutsideClass] -// CHECK:STDERR: extend impl i32 as I {} -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: -extend impl i32 as I {} - -// CHECK:STDOUT: --- fail_extend_impl_scope.carbon -// CHECK:STDOUT: -// CHECK:STDOUT: constants { -// CHECK:STDOUT: %I.type: type = facet_type <@I> [template] -// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [template] -// CHECK:STDOUT: %impl_witness: = impl_witness () [template] -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: imports { -// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { -// CHECK:STDOUT: .Int = %Core.Int -// CHECK:STDOUT: import Core//prelude -// CHECK:STDOUT: import Core//prelude/... -// CHECK:STDOUT: } -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace [template] { -// CHECK:STDOUT: .Core = imports.%Core -// CHECK:STDOUT: .I = %I.decl -// CHECK:STDOUT: } -// CHECK:STDOUT: %Core.import = import Core -// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {} -// CHECK:STDOUT: impl_decl @impl [template] {} { -// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32] -// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32] -// CHECK:STDOUT: %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type] -// CHECK:STDOUT: } -// CHECK:STDOUT: %impl_witness: = impl_witness () [template = constants.%impl_witness] -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: interface @I { -// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self] -// CHECK:STDOUT: -// CHECK:STDOUT: !members: -// CHECK:STDOUT: .Self = %Self -// CHECK:STDOUT: witness = () -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: impl @impl: %i32 as %I.ref { -// CHECK:STDOUT: !members: -// CHECK:STDOUT: witness = file.%impl_witness -// CHECK:STDOUT: } -// CHECK:STDOUT: diff --git a/toolchain/check/testdata/impl/no_prelude/fail_extend_impl_scope.carbon b/toolchain/check/testdata/impl/no_prelude/fail_extend_impl_scope.carbon new file mode 100644 index 000000000000..99b4d0750b7e --- /dev/null +++ b/toolchain/check/testdata/impl/no_prelude/fail_extend_impl_scope.carbon @@ -0,0 +1,113 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/no_prelude/fail_extend_impl_scope.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/no_prelude/fail_extend_impl_scope.carbon + +// --- fail_extend_impl_file_scope.carbon +library "[[@TEST_NAME]]"; + +interface I {} + +// CHECK:STDERR: fail_extend_impl_file_scope.carbon:[[@LINE+4]]:1: error: `extend impl` can only be used in a class [ExtendImplOutsideClass] +// CHECK:STDERR: extend impl () as I {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +extend impl () as I {} + +// --- fail_extend_impl_function_scope.carbon +library "[[@TEST_NAME]]"; + +interface J {} + +fn F() { + // CHECK:STDERR: fail_extend_impl_function_scope.carbon:[[@LINE+4]]:3: error: `extend impl` can only be used in a class [ExtendImplOutsideClass] + // CHECK:STDERR: extend impl {} as J {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + extend impl {} as J {} +} + +// CHECK:STDOUT: --- fail_extend_impl_file_scope.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %I.type: type = facet_type <@I> [template] +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [template] +// CHECK:STDOUT: %impl_witness: = impl_witness () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .I = %I.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {} +// CHECK:STDOUT: impl_decl @impl [template] {} { +// CHECK:STDOUT: %.loc9_14.1: %empty_tuple.type = tuple_literal () +// CHECK:STDOUT: %.loc9_14.2: type = converted %.loc9_14.1, constants.%empty_tuple.type [template = constants.%empty_tuple.type] +// CHECK:STDOUT: %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %impl_witness: = impl_witness () [template = constants.%impl_witness] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @I { +// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: witness = () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: %.loc9_14.2 as %I.ref { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: witness = file.%impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_extend_impl_function_scope.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %J.type: type = facet_type <@J> [template] +// CHECK:STDOUT: %Self: %J.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %impl_witness: = impl_witness () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .J = %J.decl +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %J.decl: type = interface_decl @J [template = constants.%J.type] {} {} +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @J { +// CHECK:STDOUT: %Self: %J.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = %Self +// CHECK:STDOUT: witness = () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: impl @impl: %.loc10_16.2 as %J.ref { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: witness = @F.%impl_witness +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: impl_decl @impl [template] {} { +// CHECK:STDOUT: %.loc10_16.1: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc10_16.2: type = converted %.loc10_16.1, constants.%empty_struct_type [template = constants.%empty_struct_type] +// CHECK:STDOUT: %J.ref: type = name_ref J, file.%J.decl [template = constants.%J.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %impl_witness: = impl_witness () [template = constants.%impl_witness] +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: