From de0c02ddaeba75d4b7b93a4b6cd5b956e2d44965 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 14 Dec 2023 11:51:55 -0800 Subject: [PATCH] If a name is not found in a class, perform lookup into base classes. (#3502) This builds out a little infrastructure for one name scope to `extend` another. We'll need more refinement here to cover other cases, but this should provide some foundation for that future work. --------- Co-authored-by: Jon Ross-Perkins --- toolchain/check/check.cpp | 6 +- toolchain/check/context.cpp | 76 ++++-- toolchain/check/context.h | 6 + toolchain/check/convert.cpp | 11 +- toolchain/check/convert.h | 10 +- toolchain/check/decl_name_stack.cpp | 2 +- toolchain/check/handle_class.cpp | 116 +++++--- toolchain/check/handle_name.cpp | 5 +- toolchain/check/testdata/class/base.carbon | 1 + .../check/testdata/class/base_field.carbon | 89 +++++++ .../class/base_function_unqualified.carbon | 76 ++++++ .../check/testdata/class/base_method.carbon | 90 +++++++ .../testdata/class/base_method_shadow.carbon | 134 ++++++++++ .../testdata/class/derived_to_base.carbon | 2 + .../check/testdata/class/fail_abstract.carbon | 1 + .../testdata/class/fail_base_bad_type.carbon | 249 +++++++++++++----- .../class/fail_base_method_define.carbon | 87 ++++++ .../testdata/class/fail_base_modifiers.carbon | 3 + .../testdata/class/fail_base_repeated.carbon | 2 + .../testdata/class/fail_base_unbound.carbon | 1 + .../class/fail_derived_to_base.carbon | 1 + .../testdata/class/self_conversion.carbon | 1 + .../packages/fail_api_not_found.carbon | 6 +- .../check/testdata/packages/fail_cycle.carbon | 2 +- .../fail_name_with_import_failure.carbon | 2 +- toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/sem_ir/formatter.cpp | 17 +- toolchain/sem_ir/value_stores.h | 27 +- 28 files changed, 892 insertions(+), 132 deletions(-) create mode 100644 toolchain/check/testdata/class/base_field.carbon create mode 100644 toolchain/check/testdata/class/base_function_unqualified.carbon create mode 100644 toolchain/check/testdata/class/base_method.carbon create mode 100644 toolchain/check/testdata/class/base_method_shadow.carbon create mode 100644 toolchain/check/testdata/class/fail_base_method_define.carbon diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index f2f12a4e5b07..a00b1751f128 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -87,7 +87,7 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info) if (self_import != unit_info.package_imports_map.end()) { auto& package_scope = context.name_scopes().Get(SemIR::NameScopeId::Package); - package_scope.has_load_error = self_import->second.has_load_error; + package_scope.has_error = self_import->second.has_load_error; for (const auto& import : self_import->second.imports) { const auto& import_sem_ir = **import.unit_info->unit->sem_ir; @@ -96,7 +96,7 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info) // If an import of the current package caused an error for the imported // file, it transitively affects the current file too. - package_scope.has_load_error |= import_scope.has_load_error; + package_scope.has_error |= import_scope.has_error; auto ir_id = context.sem_ir().cross_ref_irs().Add(&import_sem_ir); @@ -124,7 +124,7 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info) // Push the scope. context.PushScope(package_inst, SemIR::NameScopeId::Package, - package_scope.has_load_error); + package_scope.has_error); } else { // Push the scope; there are no names to add. context.PushScope(package_inst, SemIR::NameScopeId::Package); diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 3994f47d3e24..ef10834bdf78 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -205,7 +205,8 @@ auto Context::ResolveIfLazyImportRef(SemIR::InstId inst_id) -> void { } } -auto Context::LookupNameInDecl(Parse::NodeId parse_node, SemIR::NameId name_id, +auto Context::LookupNameInDecl(Parse::NodeId /*parse_node*/, + SemIR::NameId name_id, SemIR::NameScopeId scope_id) -> SemIR::InstId { if (scope_id == SemIR::NameScopeId::Invalid) { // Look for a name in the current scope only. There are two cases where the @@ -243,10 +244,16 @@ auto Context::LookupNameInDecl(Parse::NodeId parse_node, SemIR::NameId name_id, } return SemIR::InstId::Invalid; } else { - // TODO: Once we support `extend`, do not look into `extend`ed scopes here, - // following the same logic as above. - return LookupQualifiedName(parse_node, name_id, scope_id, - /*required=*/false); + // We do not look into `extend`ed scopes here. A qualified name in a + // declaration must specify the exact scope in which the name was originally + // introduced: + // + // base class A { fn F(); } + // class B { extend base: A; } + // + // // Error, no `F` in `B`. + // fn B.F() {} + return LookupNameInExactScope(name_id, name_scopes().Get(scope_id)); } } @@ -296,26 +303,63 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node, return SemIR::InstId::BuiltinError; } -auto Context::LookupQualifiedName(Parse::NodeId parse_node, - SemIR::NameId name_id, - SemIR::NameScopeId scope_id, bool required) +auto Context::LookupNameInExactScope(SemIR::NameId name_id, + const SemIR::NameScope& scope) -> SemIR::InstId { - CARBON_CHECK(scope_id.is_valid()) << "No scope to perform lookup into"; - const auto& scope = name_scopes().Get(scope_id); if (auto it = scope.names.find(name_id); it != scope.names.end()) { ResolveIfLazyImportRef(it->second); return it->second; } + return SemIR::InstId::Invalid; +} - // TODO: Also perform lookups into `extend`ed scopes. +auto Context::LookupQualifiedName(Parse::NodeId parse_node, + SemIR::NameId name_id, + SemIR::NameScopeId scope_id, bool required) + -> SemIR::InstId { + llvm::SmallVector scope_ids = {scope_id}; + auto result_id = SemIR::InstId::Invalid; + bool has_error = false; - if (!required) { - return SemIR::InstId::Invalid; + // Walk this scope and, if nothing is found here, the scopes it extends. + while (!scope_ids.empty()) { + const auto& scope = name_scopes().Get(scope_ids.pop_back_val()); + has_error |= scope.has_error; + + auto scope_result_id = LookupNameInExactScope(name_id, scope); + if (!scope_result_id.is_valid()) { + // Nothing found in this scope: also look in its extended scopes. + auto extended = llvm::reverse(scope.extended_scopes); + scope_ids.append(extended.begin(), extended.end()); + continue; + } + + // If this is our second lookup result, diagnose an ambiguity. + if (result_id.is_valid()) { + // TODO: This is currently not reachable because the only scope that can + // extend is a class scope, and it can only extend a single base class. + // Add test coverage once this is possible. + CARBON_DIAGNOSTIC( + NameAmbiguousDueToExtend, Error, + "Ambiguous use of name `{0}` found in multiple extended scopes.", + std::string); + emitter_->Emit(parse_node, NameAmbiguousDueToExtend, + names().GetFormatted(name_id).str()); + // TODO: Add notes pointing to the scopes. + return SemIR::InstId::BuiltinError; + } + + result_id = scope_result_id; } - if (!scope.has_load_error) { - DiagnoseNameNotFound(parse_node, name_id); + + if (required && !result_id.is_valid()) { + if (!has_error) { + DiagnoseNameNotFound(parse_node, name_id); + } + return SemIR::InstId::BuiltinError; } - return SemIR::InstId::BuiltinError; + + return result_id; } auto Context::PushScope(SemIR::InstId scope_inst_id, diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 5cfc53257e75..00dbd370e0fb 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -85,6 +85,12 @@ class Context { auto LookupUnqualifiedName(Parse::NodeId parse_node, SemIR::NameId name_id) -> SemIR::InstId; + // Performs a name lookup in a specified scope, returning the referenced + // instruction. Does not look into extended scopes. Returns an invalid + // instruction if the name is not found. + auto LookupNameInExactScope(SemIR::NameId name_id, + const SemIR::NameScope& scope) -> SemIR::InstId; + // Performs a qualified name lookup in a specified scope and in scopes that // it extends, returning the referenced instruction. auto LookupQualifiedName(Parse::NodeId parse_node, SemIR::NameId name_id, diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 3337786a11d3..1ae44f88f0db 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -1032,12 +1032,19 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id) } auto ConvertToValueOfType(Context& context, Parse::NodeId parse_node, - SemIR::InstId value_id, SemIR::TypeId type_id) + SemIR::InstId expr_id, SemIR::TypeId type_id) -> SemIR::InstId { - return Convert(context, parse_node, value_id, + return Convert(context, parse_node, expr_id, {.kind = ConversionTarget::Value, .type_id = type_id}); } +auto ConvertToValueOrRefOfType(Context& context, Parse::NodeId parse_node, + SemIR::InstId expr_id, SemIR::TypeId type_id) + -> SemIR::InstId { + return Convert(context, parse_node, expr_id, + {.kind = ConversionTarget::ValueOrRef, .type_id = type_id}); +} + auto ConvertToBoolValue(Context& context, Parse::NodeId parse_node, SemIR::InstId value_id) -> SemIR::InstId { return ConvertToValueOfType( diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 53c27fb28e26..bda956b50ff9 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -70,9 +70,15 @@ auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id) auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id) -> SemIR::InstId; -// Converts `value_id` to a value expression of type `type_id`. +// Converts `expr_id` to a value expression of type `type_id`. auto ConvertToValueOfType(Context& context, Parse::NodeId parse_node, - SemIR::InstId value_id, SemIR::TypeId type_id) + SemIR::InstId expr_id, SemIR::TypeId type_id) + -> SemIR::InstId; + +// Convert the given expression to a value or reference expression of the given +// type. +auto ConvertToValueOrRefOfType(Context& context, Parse::NodeId parse_node, + SemIR::InstId expr_id, SemIR::TypeId type_id) -> SemIR::InstId; // Converts `value_id` to a value expression of type `bool`. diff --git a/toolchain/check/decl_name_stack.cpp b/toolchain/check/decl_name_stack.cpp index d8a1a664ad7d..791a87aed418 100644 --- a/toolchain/check/decl_name_stack.cpp +++ b/toolchain/check/decl_name_stack.cpp @@ -146,7 +146,7 @@ auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context) -> void { name_context.state = NameContext::State::Resolved; name_context.target_scope_id = scope_id; context_->PushScope(name_context.resolved_inst_id, scope_id, - context_->name_scopes().Get(scope_id).has_load_error); + context_->name_scopes().Get(scope_id).has_error); break; } default: diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 49f0e3d76ead..eedec4710560 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -174,6 +174,77 @@ auto HandleBaseColon(Context& /*context*/, Parse::NodeId /*parse_node*/) return true; } +namespace { +// Information gathered about a base type specified in a `base` declaration. +struct BaseInfo { + // A `BaseInfo` representing an erroneous base. + static const BaseInfo Error; + + SemIR::TypeId type_id; + SemIR::NameScopeId scope_id; +}; +constexpr BaseInfo BaseInfo::Error = {.type_id = SemIR::TypeId::Error, + .scope_id = SemIR::NameScopeId::Invalid}; +} // namespace + +// If `type_id` is a class type, get its corresponding `SemIR::Class` object. +// Otherwise returns `nullptr`. +static auto TryGetAsClass(Context& context, SemIR::TypeId type_id) + -> SemIR::Class* { + auto class_type = context.types().TryGetAs(type_id); + if (!class_type) { + return nullptr; + } + return &context.classes().Get(class_type->class_id); +} + +// Diagnoses an attempt to derive from a final type. +static auto DiagnoseBaseIsFinal(Context& context, Parse::NodeId parse_node, + SemIR::TypeId base_type_id) -> void { + CARBON_DIAGNOSTIC(BaseIsFinal, Error, + "Deriving from final type `{0}`. Base type must be an " + "`abstract` or `base` class.", + std::string); + context.emitter().Emit(parse_node, BaseIsFinal, + context.sem_ir().StringifyType(base_type_id)); +} + +// Checks that the specified base type is valid. +static auto CheckBaseType(Context& context, Parse::NodeId parse_node, + SemIR::InstId base_expr_id) -> BaseInfo { + auto base_type_id = ExprAsType(context, parse_node, base_expr_id); + base_type_id = context.AsCompleteType(base_type_id, [&] { + CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error, + "Base `{0}` is an incomplete type.", std::string); + return context.emitter().Build( + parse_node, IncompleteTypeInBaseDecl, + context.sem_ir().StringifyType(base_type_id)); + }); + + if (base_type_id == SemIR::TypeId::Error) { + return BaseInfo::Error; + } + + auto* base_class_info = TryGetAsClass(context, base_type_id); + + // The base must not be a final class. + if (!base_class_info) { + // For now, we treat all types that aren't introduced by a `class` + // declaration as being final classes. + // TODO: Once we have a better idea of which types are considered to be + // classes, produce a better diagnostic for deriving from a non-class type. + DiagnoseBaseIsFinal(context, parse_node, base_type_id); + return BaseInfo::Error; + } + if (base_class_info->inheritance_kind == SemIR::Class::Final) { + DiagnoseBaseIsFinal(context, parse_node, base_type_id); + } + + CARBON_CHECK(base_class_info->scope_id.is_valid()) + << "Complete class should have a scope"; + return {.type_id = base_type_id, .scope_id = base_class_info->scope_id}; +} + auto HandleBaseDecl(Context& context, Parse::NodeId parse_node) -> bool { auto base_type_expr_id = context.node_stack().PopExpr(); @@ -211,54 +282,39 @@ auto HandleBaseDecl(Context& context, Parse::NodeId parse_node) -> bool { return true; } - auto base_type_id = ExprAsType(context, parse_node, base_type_expr_id); - base_type_id = context.AsCompleteType(base_type_id, [&] { - CARBON_DIAGNOSTIC(IncompleteTypeInBaseDecl, Error, - "Base `{0}` is an incomplete type.", std::string); - return context.emitter().Build( - parse_node, IncompleteTypeInBaseDecl, - context.sem_ir().StringifyType(base_type_id)); - }); - - if (base_type_id != SemIR::TypeId::Error) { - // For now, we treat all types that aren't introduced by a `class` - // declaration as being final classes. - // TODO: Once we have a better idea of which types are considered to be - // classes, produce a better diagnostic for deriving from a non-class type. - auto base_class = context.types().TryGetAs(base_type_id); - if (!base_class || - context.classes().Get(base_class->class_id).inheritance_kind == - SemIR::Class::Final) { - CARBON_DIAGNOSTIC(BaseIsFinal, Error, - "Deriving from final type `{0}`. Base type must be an " - "`abstract` or `base` class.", - std::string); - context.emitter().Emit(parse_node, BaseIsFinal, - context.sem_ir().StringifyType(base_type_id)); - } - } + auto base_info = CheckBaseType(context, parse_node, base_type_expr_id); // The `base` value in the class scope has an unbound element type. Instance // binding will be performed when it's found by name lookup into an instance. auto field_type_inst_id = context.AddInst(SemIR::UnboundElementType{ parse_node, context.GetBuiltinType(SemIR::BuiltinKind::TypeType), - class_info.self_type_id, base_type_id}); + class_info.self_type_id, base_info.type_id}); auto field_type_id = context.CanonicalizeType(field_type_inst_id); class_info.base_id = context.AddInst(SemIR::BaseDecl{ - parse_node, field_type_id, base_type_id, + parse_node, field_type_id, base_info.type_id, SemIR::ElementIndex( context.args_type_info_stack().PeekCurrentBlockContents().size())}); // Add a corresponding field to the object representation of the class. // TODO: Consider whether we want to use `partial T` here. - context.args_type_info_stack().AddInst( - SemIR::StructTypeField{parse_node, SemIR::NameId::Base, base_type_id}); + context.args_type_info_stack().AddInst(SemIR::StructTypeField{ + parse_node, SemIR::NameId::Base, base_info.type_id}); // Bind the name `base` in the class to the base field. context.decl_name_stack().AddNameToLookup( context.decl_name_stack().MakeUnqualifiedName(parse_node, SemIR::NameId::Base), class_info.base_id); + + // Extend the class scope with the base class. + if (!!(modifiers & KeywordModifierSet::Extend)) { + auto& class_scope = context.name_scopes().Get(class_info.scope_id); + if (base_info.scope_id.is_valid()) { + class_scope.extended_scopes.push_back(base_info.scope_id); + } else { + class_scope.has_error = true; + } + } return true; } diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 29795b275f7a..b84c87bba445 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -131,8 +131,9 @@ auto HandleMemberAccessExpr(Context& context, Parse::NodeId parse_node) if (auto unbound_element_type = context.types().TryGetAs( member_type_id)) { - // TODO: Check that the unbound element type describes a member of this - // class. Perform a conversion of the base if necessary. + // Convert the base to the type of the element if necessary. + base_id = ConvertToValueOrRefOfType( + context, parse_node, base_id, unbound_element_type->class_type_id); // Find the specified element, which could be either a field or a base // class, and build an element access expression. diff --git a/toolchain/check/testdata/class/base.carbon b/toolchain/check/testdata/class/base.carbon index a27edaca7da3..08b29f6026ea 100644 --- a/toolchain/check/testdata/class/base.carbon +++ b/toolchain/check/testdata/class/base.carbon @@ -67,6 +67,7 @@ fn Access(d: Derived) -> (i32, i32) { // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc12_20.2 // CHECK:STDOUT: .d = %d +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Make() -> %return: Derived { diff --git a/toolchain/check/testdata/class/base_field.carbon b/toolchain/check/testdata/class/base_field.carbon new file mode 100644 index 000000000000..fe998bf41042 --- /dev/null +++ b/toolchain/check/testdata/class/base_field.carbon @@ -0,0 +1,89 @@ +// 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 + +base class Base { + var a: i32; + var b: i32; + var c: i32; +} + +class Derived { + extend base: Base; + + var d: i32; + var e: i32; +} + +fn Access(p: Derived*) -> i32* { + return &(*p).c; +} + +// CHECK:STDOUT: --- base_field.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc11_1.1: type = struct_type {.a: i32, .b: i32, .c: i32} +// CHECK:STDOUT: %.loc11_1.2: type = ptr_type {.a: i32, .b: i32, .c: i32} +// CHECK:STDOUT: %.loc18_1.1: type = struct_type {.base: Base, .d: i32, .e: i32} +// CHECK:STDOUT: %.loc18_1.2: type = struct_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32} +// CHECK:STDOUT: %.loc18_1.3: type = ptr_type {.base: {.a: i32, .b: i32, .c: i32}*, .d: i32, .e: i32} +// CHECK:STDOUT: %.loc13: type = ptr_type {.base: Base, .d: i32, .e: i32} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Derived = %Derived.decl, .Access = %Access} +// CHECK:STDOUT: %Base.decl = class_decl @Base, () +// CHECK:STDOUT: %Base: type = class_type @Base +// CHECK:STDOUT: %Derived.decl = class_decl @Derived, () +// CHECK:STDOUT: %Derived: type = class_type @Derived +// CHECK:STDOUT: %Access: = fn_decl @Access +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_element_type Base, i32 +// CHECK:STDOUT: %.loc8_8.2: = field_decl a, element0 +// CHECK:STDOUT: %a: = bind_name a, %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_element_type Base, i32 +// CHECK:STDOUT: %.loc9_8.2: = field_decl b, element1 +// CHECK:STDOUT: %b: = bind_name b, %.loc9_8.2 +// CHECK:STDOUT: %.loc10_8.1: type = unbound_element_type Base, i32 +// CHECK:STDOUT: %.loc10_8.2: = field_decl c, element2 +// CHECK:STDOUT: %c: = bind_name c, %.loc10_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: .c = %c +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base +// CHECK:STDOUT: %.loc14_20.1: type = unbound_element_type Derived, Base +// CHECK:STDOUT: %.loc14_20.2: = base_decl Base, element0 +// CHECK:STDOUT: %.loc16_8.1: type = unbound_element_type Derived, i32 +// CHECK:STDOUT: %.loc16_8.2: = field_decl d, element1 +// CHECK:STDOUT: %d: = bind_name d, %.loc16_8.2 +// CHECK:STDOUT: %.loc17_8.1: type = unbound_element_type Derived, i32 +// CHECK:STDOUT: %.loc17_8.2: = field_decl e, element2 +// CHECK:STDOUT: %e: = bind_name e, %.loc17_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc14_20.2 +// CHECK:STDOUT: .d = %d +// CHECK:STDOUT: .e = %e +// CHECK:STDOUT: extend name_scope1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Access(%p: Derived*) -> i32* { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: Derived* = name_ref p, %p +// CHECK:STDOUT: %.loc21_12: ref Derived = deref %p.ref +// CHECK:STDOUT: %.loc21_15.1: ref Base = class_element_access %.loc21_12, element0 +// CHECK:STDOUT: %.loc21_15.2: ref Base = converted %.loc21_12, %.loc21_15.1 +// CHECK:STDOUT: %.loc21_15.3: ref i32 = class_element_access %.loc21_15.2, element2 +// CHECK:STDOUT: %.loc21_10: i32* = address_of %.loc21_15.3 +// CHECK:STDOUT: return %.loc21_10 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/base_function_unqualified.carbon b/toolchain/check/testdata/class/base_function_unqualified.carbon new file mode 100644 index 000000000000..d5c0f73dc8b7 --- /dev/null +++ b/toolchain/check/testdata/class/base_function_unqualified.carbon @@ -0,0 +1,76 @@ +// 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 + +base class Base { + fn F(); +} + +class Derived { + extend base: Base; + + fn G() { F(); } + fn H(); +} + +fn Derived.H() { + F(); +} + +// CHECK:STDOUT: --- base_function_unqualified.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc9_1.1: type = struct_type {} +// CHECK:STDOUT: %.loc9_1.2: type = tuple_type () +// CHECK:STDOUT: %.loc7: type = ptr_type {} +// CHECK:STDOUT: %.loc16: type = struct_type {.base: Base} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Derived = %Derived.decl} +// CHECK:STDOUT: %Base.decl = class_decl @Base, () +// CHECK:STDOUT: %Base: type = class_type @Base +// CHECK:STDOUT: %Derived.decl = class_decl @Derived, () +// CHECK:STDOUT: %Derived: type = class_type @Derived +// CHECK:STDOUT: %H: = fn_decl @H +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base +// CHECK:STDOUT: %.loc12_20.1: type = unbound_element_type Derived, Base +// CHECK:STDOUT: %.loc12_20.2: = base_decl Base, element0 +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: %H: = fn_decl @H +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc12_20.2 +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: .H = %H +// CHECK:STDOUT: extend name_scope1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: = name_ref F, @Base.%F +// CHECK:STDOUT: %.loc14: init () = call %F.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: = name_ref F, @Base.%F +// CHECK:STDOUT: %.loc19: init () = call %F.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/base_method.carbon b/toolchain/check/testdata/class/base_method.carbon new file mode 100644 index 000000000000..26cbfc101697 --- /dev/null +++ b/toolchain/check/testdata/class/base_method.carbon @@ -0,0 +1,90 @@ +// 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 + +base class Base { + var a: i32; + + fn F[addr self: Self*](); +} + +fn Base.F[addr self: Base*]() { + (*self).a = 1; +} + +class Derived { + extend base: Base; +} + +fn Call(p: Derived*) { + (*p).F(); +} + +// CHECK:STDOUT: --- base_method.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc11: type = struct_type {.a: i32} +// CHECK:STDOUT: %.loc7: type = ptr_type {.a: i32} +// CHECK:STDOUT: %.loc19_1.1: type = struct_type {.base: Base} +// CHECK:STDOUT: %.loc19_1.2: type = struct_type {.base: {.a: i32}*} +// CHECK:STDOUT: %.loc17: type = ptr_type {.base: Base} +// CHECK:STDOUT: %.loc22: type = tuple_type () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Derived = %Derived.decl, .Call = %Call} +// CHECK:STDOUT: %Base.decl = class_decl @Base, () +// CHECK:STDOUT: %Base: type = class_type @Base +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %Derived.decl = class_decl @Derived, () +// CHECK:STDOUT: %Derived: type = class_type @Derived +// CHECK:STDOUT: %Call: = fn_decl @Call +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_element_type Base, i32 +// CHECK:STDOUT: %.loc8_8.2: = field_decl a, element0 +// CHECK:STDOUT: %a: = bind_name a, %.loc8_8.2 +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base +// CHECK:STDOUT: %.loc18_20.1: type = unbound_element_type Derived, Base +// CHECK:STDOUT: %.loc18_20.2: = base_decl Base, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc18_20.2 +// CHECK:STDOUT: extend name_scope1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F[%self.addr: Base*]() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %self.ref: Base* = name_ref self, %self.addr +// CHECK:STDOUT: %.loc14_4: ref Base = deref %self.ref +// CHECK:STDOUT: %.loc14_10: ref i32 = class_element_access %.loc14_4, element0 +// CHECK:STDOUT: %.loc14_15: i32 = int_literal 1 +// CHECK:STDOUT: assign %.loc14_10, %.loc14_15 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Call(%p: Derived*) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: Derived* = name_ref p, %p +// CHECK:STDOUT: %.loc22_4.1: ref Derived = deref %p.ref +// CHECK:STDOUT: %.loc22_7: = bound_method %.loc22_4.1, @Base.%F +// CHECK:STDOUT: %.loc22_4.2: Derived* = address_of %.loc22_4.1 +// CHECK:STDOUT: %.loc22_9.1: ref Derived = deref %.loc22_4.2 +// CHECK:STDOUT: %.loc22_9.2: ref Base = class_element_access %.loc22_9.1, element0 +// CHECK:STDOUT: %.loc22_9.3: Base* = address_of %.loc22_9.2 +// CHECK:STDOUT: %.loc22_9.4: Base* = converted %.loc22_4.2, %.loc22_9.3 +// CHECK:STDOUT: %.loc22_9.5: init () = call %.loc22_7(%.loc22_9.4) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/base_method_shadow.carbon b/toolchain/check/testdata/class/base_method_shadow.carbon new file mode 100644 index 000000000000..b7062c63aa05 --- /dev/null +++ b/toolchain/check/testdata/class/base_method_shadow.carbon @@ -0,0 +1,134 @@ +// 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 + +base class A { + fn F[addr self: Self*](); +} + +base class B { + extend base: A; + fn F[addr self: Self*](); +} + +class C { + extend base: B; + fn F[addr self: Self*](); +} + +class D { + extend base: B; +} + +fn Call(a: A*, b: B*, c: C*, d: D*) { + (*a).F(); + (*b).F(); + (*c).F(); + (*d).F(); +} + +// CHECK:STDOUT: --- base_method_shadow.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc9_1.1: type = struct_type {} +// CHECK:STDOUT: %.loc9_1.2: type = tuple_type () +// CHECK:STDOUT: %.loc7: type = ptr_type {} +// CHECK:STDOUT: %.loc14_1.1: type = struct_type {.base: A} +// CHECK:STDOUT: %.loc14_1.2: type = struct_type {.base: {}*} +// CHECK:STDOUT: %.loc11: type = ptr_type {.base: A} +// CHECK:STDOUT: %.loc19_1.1: type = struct_type {.base: B} +// CHECK:STDOUT: %.loc19_1.2: type = struct_type {.base: {.base: A}*} +// CHECK:STDOUT: %.loc16: type = ptr_type {.base: B} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.A = %A.decl, .B = %B.decl, .C = %C.decl, .D = %D.decl, .Call = %Call} +// CHECK:STDOUT: %A.decl = class_decl @A, () +// CHECK:STDOUT: %A: type = class_type @A +// CHECK:STDOUT: %B.decl = class_decl @B, () +// CHECK:STDOUT: %B: type = class_type @B +// CHECK:STDOUT: %C.decl = class_decl @C, () +// CHECK:STDOUT: %C: type = class_type @C +// CHECK:STDOUT: %D.decl = class_decl @D, () +// CHECK:STDOUT: %D: type = class_type @D +// CHECK:STDOUT: %Call: = fn_decl @Call +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @A { +// CHECK:STDOUT: %F: = fn_decl @F.1 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: %A.ref: type = name_ref A, file.%A +// CHECK:STDOUT: %.loc12_17.1: type = unbound_element_type B, A +// CHECK:STDOUT: %.loc12_17.2: = base_decl A, element0 +// CHECK:STDOUT: %F: = fn_decl @F.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc12_17.2 +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: extend name_scope1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B +// CHECK:STDOUT: %.loc17_17.1: type = unbound_element_type C, B +// CHECK:STDOUT: %.loc17_17.2: = base_decl B, element0 +// CHECK:STDOUT: %F: = fn_decl @F.3 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc17_17.2 +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: extend name_scope2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @D { +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B +// CHECK:STDOUT: %.loc22_17.1: type = unbound_element_type D, B +// CHECK:STDOUT: %.loc22_17.2: = base_decl B, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc22_17.2 +// CHECK:STDOUT: extend name_scope2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1[%self.addr: A*](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2[%self.addr: B*](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.3[%self.addr: C*](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Call(%a: A*, %b: B*, %c: C*, %d: D*) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %a.ref: A* = name_ref a, %a +// CHECK:STDOUT: %.loc26_4.1: ref A = deref %a.ref +// CHECK:STDOUT: %.loc26_7: = bound_method %.loc26_4.1, @A.%F +// CHECK:STDOUT: %.loc26_4.2: A* = address_of %.loc26_4.1 +// CHECK:STDOUT: %.loc26_9: init () = call %.loc26_7(%.loc26_4.2) +// CHECK:STDOUT: %b.ref: B* = name_ref b, %b +// CHECK:STDOUT: %.loc27_4.1: ref B = deref %b.ref +// CHECK:STDOUT: %.loc27_7: = bound_method %.loc27_4.1, @B.%F +// CHECK:STDOUT: %.loc27_4.2: B* = address_of %.loc27_4.1 +// CHECK:STDOUT: %.loc27_9: init () = call %.loc27_7(%.loc27_4.2) +// CHECK:STDOUT: %c.ref: C* = name_ref c, %c +// CHECK:STDOUT: %.loc28_4.1: ref C = deref %c.ref +// CHECK:STDOUT: %.loc28_7: = bound_method %.loc28_4.1, @C.%F +// CHECK:STDOUT: %.loc28_4.2: C* = address_of %.loc28_4.1 +// CHECK:STDOUT: %.loc28_9: init () = call %.loc28_7(%.loc28_4.2) +// CHECK:STDOUT: %d.ref: D* = name_ref d, %d +// CHECK:STDOUT: %.loc29_4.1: ref D = deref %d.ref +// CHECK:STDOUT: %.loc29_7: = bound_method %.loc29_4.1, @B.%F +// CHECK:STDOUT: %.loc29_4.2: D* = address_of %.loc29_4.1 +// CHECK:STDOUT: %.loc29_9.1: ref D = deref %.loc29_4.2 +// CHECK:STDOUT: %.loc29_9.2: ref B = class_element_access %.loc29_9.1, element0 +// CHECK:STDOUT: %.loc29_9.3: B* = address_of %.loc29_9.2 +// CHECK:STDOUT: %.loc29_9.4: B* = converted %.loc29_4.2, %.loc29_9.3 +// CHECK:STDOUT: %.loc29_9.5: init () = call %.loc29_7(%.loc29_9.4) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/derived_to_base.carbon b/toolchain/check/testdata/class/derived_to_base.carbon index 0b9a54fcb83c..f705cd7391ca 100644 --- a/toolchain/check/testdata/class/derived_to_base.carbon +++ b/toolchain/check/testdata/class/derived_to_base.carbon @@ -87,6 +87,7 @@ fn ConvertInit() { // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc12_17.2 // CHECK:STDOUT: .b = %b +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @C { @@ -100,6 +101,7 @@ fn ConvertInit() { // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc17_17.2 // CHECK:STDOUT: .c = %c +// CHECK:STDOUT: extend name_scope2 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertCToB(%p: C*) -> B* { diff --git a/toolchain/check/testdata/class/fail_abstract.carbon b/toolchain/check/testdata/class/fail_abstract.carbon index fb5222a0717a..decb4e873c0f 100644 --- a/toolchain/check/testdata/class/fail_abstract.carbon +++ b/toolchain/check/testdata/class/fail_abstract.carbon @@ -71,6 +71,7 @@ fn Access(d: Derived) -> (i32, i32) { // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc12_24.2 // CHECK:STDOUT: .d = %d +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Make() -> %return: Derived { diff --git a/toolchain/check/testdata/class/fail_base_bad_type.carbon b/toolchain/check/testdata/class/fail_base_bad_type.carbon index cc911f2fc037..41aee7ce2c2c 100644 --- a/toolchain/check/testdata/class/fail_base_bad_type.carbon +++ b/toolchain/check/testdata/class/fail_base_bad_type.carbon @@ -5,7 +5,19 @@ // AUTOUPDATE base class Base {} -class Final {} +class Final { + var a: i32; +} + +class DeriveFromError { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:16: ERROR: Name `error` not found. + // CHECK:STDERR: extend base: error; + // CHECK:STDERR: ^~~~~ + extend base: error; +} + +// This should not produce an error. +fn AccessMemberWithInvalidBaseError(p: DeriveFromError*) -> i32 { return (*p).n; } class DeriveFromNonType { // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Cannot implicitly convert from `i32` to `type`. @@ -14,6 +26,8 @@ class DeriveFromNonType { extend base: 32; } +fn AccessMemberWithInvalidBasNonType(p: DeriveFromNonType*) -> i32 { return (*p).n; } + class DeriveFromi32 { // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Deriving from final type `i32`. Base type must be an `abstract` or `base` class. // CHECK:STDERR: extend base: i32; @@ -23,8 +37,13 @@ class DeriveFromi32 { // It's not really important whether this conversion produces an error or not, // but it shouldn't crash. +// CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:53: ERROR: Cannot implicitly convert from `DeriveFromi32*` to `i32*`. +// CHECK:STDERR: fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; } +// CHECK:STDERR: ^~~~~~~~~ fn ConvertToBadBasei32(p: DeriveFromi32*) -> i32* { return p; } +fn AccessMemberWithInvalidBasei32(p: DeriveFromi32*) -> i32 { return (*p).n; } + class DeriveFromTuple { // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Deriving from final type `(Base,)`. Base type must be an `abstract` or `base` class. // CHECK:STDERR: extend base: (Base,); @@ -32,8 +51,13 @@ class DeriveFromTuple { extend base: (Base,); } +// CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:61: ERROR: Cannot implicitly convert from `DeriveFromTuple*` to `(Base,)*`. +// CHECK:STDERR: fn ConvertToBadBaseTuple(p: DeriveFromTuple*) -> (Base,)* { return p; } +// CHECK:STDERR: ^~~~~~~~~ fn ConvertToBadBaseTuple(p: DeriveFromTuple*) -> (Base,)* { return p; } +fn AccessMemberWithInvalidBaseTuple(p: DeriveFromTuple*) -> i32 { return (*p).n; } + // TODO: Should we allow this? // We do allow `{.base = {.a: i32, .b: i32}}`. class DeriveFromStruct { @@ -43,8 +67,14 @@ class DeriveFromStruct { extend base: {.a: i32, .b: i32}; } +// CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:74: ERROR: Cannot implicitly convert from `DeriveFromStruct*` to `{.a: i32, .b: i32}*`. +// CHECK:STDERR: fn ConvertToBadBaseStruct(p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { return p; } +// CHECK:STDERR: ^~~~~~~~~ fn ConvertToBadBaseStruct(p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { return p; } +// It would be OK to reject this if we start actually looking in the struct type. +fn AccessMemberWithInvalidBaseStruct(p: DeriveFromStruct*) -> i32 { return (*p).n; } + base class Incomplete; class DeriveFromIncomplete { @@ -62,6 +92,8 @@ class DeriveFromIncomplete { // CHECK:STDERR: ^~~~~~~~~ fn ConvertToBadBaseIncomplete(p: DeriveFromIncomplete*) -> Incomplete* { return p; } +fn AccessMemberWithInvalidBaseIncomplete(p: DeriveFromIncomplete*) -> i32 { return (*p).n; } + class DeriveFromFinal { // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:3: ERROR: Deriving from final type `Final`. Base type must be an `abstract` or `base` class. // CHECK:STDERR: extend base: Final; @@ -69,58 +101,76 @@ class DeriveFromFinal { extend base: Final; } -fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { return p; } +// For error recovery purposes, we derive from the final type anyway. +fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { + return p; +} + +fn AccessMemberWithInvalidBaseFinal_WithMember(p: DeriveFromFinal*) -> i32 { + return (*p).a; +} + +fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 { + // CHECK:STDERR: fail_base_bad_type.carbon:[[@LINE+3]]:10: ERROR: Name `b` not found. + // CHECK:STDERR: return (*p).b; + // CHECK:STDERR: ^~~~~~ + return (*p).b; +} // CHECK:STDOUT: --- fail_base_bad_type.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %.loc7_18.1: type = struct_type {} -// CHECK:STDOUT: %.loc15: type = struct_type {.base: } -// CHECK:STDOUT: %.loc22: type = struct_type {.base: i32} -// CHECK:STDOUT: %.loc17: type = ptr_type {.base: i32} -// CHECK:STDOUT: %.loc32_22: type = tuple_type (type) -// CHECK:STDOUT: %.loc32_23.1: type = tuple_type (Base) +// CHECK:STDOUT: %.loc10: type = struct_type {.a: i32} +// CHECK:STDOUT: %.loc17: type = struct_type {.base: } +// CHECK:STDOUT: %.loc12: type = ptr_type {.base: } +// CHECK:STDOUT: %.loc51_22: type = tuple_type (type) +// CHECK:STDOUT: %.loc51_23.1: type = tuple_type (Base) // CHECK:STDOUT: %.loc7_18.2: type = tuple_type () // CHECK:STDOUT: %.loc7_17: type = ptr_type {} -// CHECK:STDOUT: %.loc32_23.2: type = tuple_type ({}*) -// CHECK:STDOUT: %.loc33_1.1: type = struct_type {.base: (Base,)} -// CHECK:STDOUT: %.loc33_1.2: type = struct_type {.base: ({}*,)} -// CHECK:STDOUT: %.loc28: type = ptr_type {.base: (Base,)} -// CHECK:STDOUT: %.loc43: type = ptr_type {.a: i32, .b: i32} -// CHECK:STDOUT: %.loc44_1.1: type = struct_type {.base: {.a: i32, .b: i32}} -// CHECK:STDOUT: %.loc44_1.2: type = struct_type {.base: {.a: i32, .b: i32}*} -// CHECK:STDOUT: %.loc39: type = ptr_type {.base: {.a: i32, .b: i32}} -// CHECK:STDOUT: %.loc50: type = ptr_type {.base: } -// CHECK:STDOUT: %.loc70_1.1: type = struct_type {.base: Final} -// CHECK:STDOUT: %.loc70_1.2: type = struct_type {.base: {}*} -// CHECK:STDOUT: %.loc65: type = ptr_type {.base: Final} +// CHECK:STDOUT: %.loc51_23.2: type = tuple_type ({}*) +// CHECK:STDOUT: %.loc67: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc8: type = ptr_type {.a: i32} +// CHECK:STDOUT: %.loc102_1.1: type = struct_type {.base: Final} +// CHECK:STDOUT: %.loc102_1.2: type = struct_type {.base: {.a: i32}*} +// CHECK:STDOUT: %.loc97: type = ptr_type {.base: Final} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Final = %Final.decl, .DeriveFromNonType = %DeriveFromNonType.decl, .DeriveFromi32 = %DeriveFromi32.decl, .ConvertToBadBasei32 = %ConvertToBadBasei32, .DeriveFromTuple = %DeriveFromTuple.decl, .ConvertToBadBaseTuple = %ConvertToBadBaseTuple, .DeriveFromStruct = %DeriveFromStruct.decl, .ConvertToBadBaseStruct = %ConvertToBadBaseStruct, .Incomplete = %Incomplete.decl, .DeriveFromIncomplete = %DeriveFromIncomplete.decl, .ConvertToBadBaseIncomplete = %ConvertToBadBaseIncomplete, .DeriveFromFinal = %DeriveFromFinal.decl, .ConvertToBadBaseFinal = %ConvertToBadBaseFinal} +// CHECK:STDOUT: package: = namespace {.Base = %Base.decl, .Final = %Final.decl, .DeriveFromError = %DeriveFromError.decl, .AccessMemberWithInvalidBaseError = %AccessMemberWithInvalidBaseError, .DeriveFromNonType = %DeriveFromNonType.decl, .AccessMemberWithInvalidBasNonType = %AccessMemberWithInvalidBasNonType, .DeriveFromi32 = %DeriveFromi32.decl, .ConvertToBadBasei32 = %ConvertToBadBasei32, .AccessMemberWithInvalidBasei32 = %AccessMemberWithInvalidBasei32, .DeriveFromTuple = %DeriveFromTuple.decl, .ConvertToBadBaseTuple = %ConvertToBadBaseTuple, .AccessMemberWithInvalidBaseTuple = %AccessMemberWithInvalidBaseTuple, .DeriveFromStruct = %DeriveFromStruct.decl, .ConvertToBadBaseStruct = %ConvertToBadBaseStruct, .AccessMemberWithInvalidBaseStruct = %AccessMemberWithInvalidBaseStruct, .Incomplete = %Incomplete.decl, .DeriveFromIncomplete = %DeriveFromIncomplete.decl, .ConvertToBadBaseIncomplete = %ConvertToBadBaseIncomplete, .AccessMemberWithInvalidBaseIncomplete = %AccessMemberWithInvalidBaseIncomplete, .DeriveFromFinal = %DeriveFromFinal.decl, .ConvertToBadBaseFinal = %ConvertToBadBaseFinal, .AccessMemberWithInvalidBaseFinal_WithMember = %AccessMemberWithInvalidBaseFinal_WithMember, .AccessMemberWithInvalidBaseFinal_NoMember = %AccessMemberWithInvalidBaseFinal_NoMember} // CHECK:STDOUT: %Base.decl = class_decl @Base, () // CHECK:STDOUT: %Base: type = class_type @Base // CHECK:STDOUT: %Final.decl = class_decl @Final, () // CHECK:STDOUT: %Final: type = class_type @Final +// CHECK:STDOUT: %DeriveFromError.decl = class_decl @DeriveFromError, () +// CHECK:STDOUT: %DeriveFromError: type = class_type @DeriveFromError +// CHECK:STDOUT: %AccessMemberWithInvalidBaseError: = fn_decl @AccessMemberWithInvalidBaseError // CHECK:STDOUT: %DeriveFromNonType.decl = class_decl @DeriveFromNonType, () // CHECK:STDOUT: %DeriveFromNonType: type = class_type @DeriveFromNonType +// CHECK:STDOUT: %AccessMemberWithInvalidBasNonType: = fn_decl @AccessMemberWithInvalidBasNonType // CHECK:STDOUT: %DeriveFromi32.decl = class_decl @DeriveFromi32, () // CHECK:STDOUT: %DeriveFromi32: type = class_type @DeriveFromi32 // CHECK:STDOUT: %ConvertToBadBasei32: = fn_decl @ConvertToBadBasei32 +// CHECK:STDOUT: %AccessMemberWithInvalidBasei32: = fn_decl @AccessMemberWithInvalidBasei32 // CHECK:STDOUT: %DeriveFromTuple.decl = class_decl @DeriveFromTuple, () // CHECK:STDOUT: %DeriveFromTuple: type = class_type @DeriveFromTuple // CHECK:STDOUT: %ConvertToBadBaseTuple: = fn_decl @ConvertToBadBaseTuple +// CHECK:STDOUT: %AccessMemberWithInvalidBaseTuple: = fn_decl @AccessMemberWithInvalidBaseTuple // CHECK:STDOUT: %DeriveFromStruct.decl = class_decl @DeriveFromStruct, () // CHECK:STDOUT: %DeriveFromStruct: type = class_type @DeriveFromStruct // CHECK:STDOUT: %ConvertToBadBaseStruct: = fn_decl @ConvertToBadBaseStruct +// CHECK:STDOUT: %AccessMemberWithInvalidBaseStruct: = fn_decl @AccessMemberWithInvalidBaseStruct // CHECK:STDOUT: %Incomplete.decl = class_decl @Incomplete, () // CHECK:STDOUT: %Incomplete: type = class_type @Incomplete // CHECK:STDOUT: %DeriveFromIncomplete.decl = class_decl @DeriveFromIncomplete, () // CHECK:STDOUT: %DeriveFromIncomplete: type = class_type @DeriveFromIncomplete // CHECK:STDOUT: %ConvertToBadBaseIncomplete: = fn_decl @ConvertToBadBaseIncomplete +// CHECK:STDOUT: %AccessMemberWithInvalidBaseIncomplete: = fn_decl @AccessMemberWithInvalidBaseIncomplete // CHECK:STDOUT: %DeriveFromFinal.decl = class_decl @DeriveFromFinal, () // CHECK:STDOUT: %DeriveFromFinal: type = class_type @DeriveFromFinal // CHECK:STDOUT: %ConvertToBadBaseFinal: = fn_decl @ConvertToBadBaseFinal +// CHECK:STDOUT: %AccessMemberWithInvalidBaseFinal_WithMember: = fn_decl @AccessMemberWithInvalidBaseFinal_WithMember +// CHECK:STDOUT: %AccessMemberWithInvalidBaseFinal_NoMember: = fn_decl @AccessMemberWithInvalidBaseFinal_NoMember // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Base { @@ -129,95 +179,143 @@ fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { return p; } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Final { +// CHECK:STDOUT: %.loc9_8.1: type = unbound_element_type Final, i32 +// CHECK:STDOUT: %.loc9_8.2: = field_decl a, element0 +// CHECK:STDOUT: %a: = bind_name a, %.loc9_8.2 // CHECK:STDOUT: // CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @DeriveFromError { +// CHECK:STDOUT: %error.ref: = name_ref error, +// CHECK:STDOUT: %.loc16_21.1: type = unbound_element_type DeriveFromError, +// CHECK:STDOUT: %.loc16_21.2: = base_decl , element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc16_21.2 +// CHECK:STDOUT: has_error // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @DeriveFromNonType { -// CHECK:STDOUT: %.loc14_16: i32 = int_literal 32 -// CHECK:STDOUT: %.loc14_18.1: type = unbound_element_type DeriveFromNonType, -// CHECK:STDOUT: %.loc14_18.2: = base_decl , element0 +// CHECK:STDOUT: %.loc26_16: i32 = int_literal 32 +// CHECK:STDOUT: %.loc26_18.1: type = unbound_element_type DeriveFromNonType, +// CHECK:STDOUT: %.loc26_18.2: = base_decl , element0 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .base = %.loc14_18.2 +// CHECK:STDOUT: .base = %.loc26_18.2 +// CHECK:STDOUT: has_error // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @DeriveFromi32 { -// CHECK:STDOUT: %.loc21_19.1: type = unbound_element_type DeriveFromi32, i32 -// CHECK:STDOUT: %.loc21_19.2: = base_decl i32, element0 +// CHECK:STDOUT: %.loc35_19.1: type = unbound_element_type DeriveFromi32, +// CHECK:STDOUT: %.loc35_19.2: = base_decl , element0 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .base = %.loc21_19.2 +// CHECK:STDOUT: .base = %.loc35_19.2 +// CHECK:STDOUT: has_error // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @DeriveFromTuple { // CHECK:STDOUT: %Base.ref: type = name_ref Base, file.%Base -// CHECK:STDOUT: %.loc32_22: (type,) = tuple_literal (%Base.ref) -// CHECK:STDOUT: %.loc32_23.1: type = converted %.loc32_22, constants.%.loc32_23.1 -// CHECK:STDOUT: %.loc32_23.2: type = unbound_element_type DeriveFromTuple, (Base,) -// CHECK:STDOUT: %.loc32_23.3: = base_decl (Base,), element0 +// CHECK:STDOUT: %.loc51_22: (type,) = tuple_literal (%Base.ref) +// CHECK:STDOUT: %.loc51_23.1: type = converted %.loc51_22, constants.%.loc51_23.1 +// CHECK:STDOUT: %.loc51_23.2: type = unbound_element_type DeriveFromTuple, +// CHECK:STDOUT: %.loc51_23.3: = base_decl , element0 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .base = %.loc32_23.3 +// CHECK:STDOUT: .base = %.loc51_23.3 +// CHECK:STDOUT: has_error // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @DeriveFromStruct { -// CHECK:STDOUT: %.loc43_33: type = struct_type {.a: i32, .b: i32} -// CHECK:STDOUT: %.loc43_34.1: type = unbound_element_type DeriveFromStruct, {.a: i32, .b: i32} -// CHECK:STDOUT: %.loc43_34.2: = base_decl {.a: i32, .b: i32}, element0 +// CHECK:STDOUT: %.loc67_33: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc67_34.1: type = unbound_element_type DeriveFromStruct, +// CHECK:STDOUT: %.loc67_34.2: = base_decl , element0 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .base = %.loc43_34.2 +// CHECK:STDOUT: .base = %.loc67_34.2 +// CHECK:STDOUT: has_error // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Incomplete; // CHECK:STDOUT: // CHECK:STDOUT: class @DeriveFromIncomplete { // CHECK:STDOUT: %Incomplete.ref: type = name_ref Incomplete, file.%Incomplete -// CHECK:STDOUT: %.loc57_26.1: type = unbound_element_type DeriveFromIncomplete, -// CHECK:STDOUT: %.loc57_26.2: = base_decl , element0 +// CHECK:STDOUT: %.loc87_26.1: type = unbound_element_type DeriveFromIncomplete, +// CHECK:STDOUT: %.loc87_26.2: = base_decl , element0 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .base = %.loc57_26.2 +// CHECK:STDOUT: .base = %.loc87_26.2 +// CHECK:STDOUT: has_error // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @DeriveFromFinal { // CHECK:STDOUT: %Final.ref: type = name_ref Final, file.%Final -// CHECK:STDOUT: %.loc69_21.1: type = unbound_element_type DeriveFromFinal, Final -// CHECK:STDOUT: %.loc69_21.2: = base_decl Final, element0 +// CHECK:STDOUT: %.loc101_21.1: type = unbound_element_type DeriveFromFinal, Final +// CHECK:STDOUT: %.loc101_21.2: = base_decl Final, element0 // CHECK:STDOUT: // CHECK:STDOUT: !members: -// CHECK:STDOUT: .base = %.loc69_21.2 +// CHECK:STDOUT: .base = %.loc101_21.2 +// CHECK:STDOUT: extend name_scope2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseError(%p: DeriveFromError*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromError* = name_ref p, %p +// CHECK:STDOUT: %.loc20: ref DeriveFromError = deref %p.ref +// CHECK:STDOUT: %n.ref: = name_ref n, +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBasNonType(%p: DeriveFromNonType*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromNonType* = name_ref p, %p +// CHECK:STDOUT: %.loc29: ref DeriveFromNonType = deref %p.ref +// CHECK:STDOUT: %n.ref: = name_ref n, +// CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertToBadBasei32(%p: DeriveFromi32*) -> i32* { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: DeriveFromi32* = name_ref p, %p -// CHECK:STDOUT: %.loc26_61.1: ref DeriveFromi32 = deref %p.ref -// CHECK:STDOUT: %.loc26_61.2: ref i32 = class_element_access %.loc26_61.1, element0 -// CHECK:STDOUT: %.loc26_61.3: i32* = address_of %.loc26_61.2 -// CHECK:STDOUT: %.loc26_61.4: i32* = converted %p.ref, %.loc26_61.3 -// CHECK:STDOUT: return %.loc26_61.4 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBasei32(%p: DeriveFromi32*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromi32* = name_ref p, %p +// CHECK:STDOUT: %.loc45: ref DeriveFromi32 = deref %p.ref +// CHECK:STDOUT: %n.ref: = name_ref n, +// CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertToBadBaseTuple(%p: DeriveFromTuple*) -> (Base,)* { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: DeriveFromTuple* = name_ref p, %p -// CHECK:STDOUT: %.loc35_69.1: ref DeriveFromTuple = deref %p.ref -// CHECK:STDOUT: %.loc35_69.2: ref (Base,) = class_element_access %.loc35_69.1, element0 -// CHECK:STDOUT: %.loc35_69.3: (Base,)* = address_of %.loc35_69.2 -// CHECK:STDOUT: %.loc35_69.4: (Base,)* = converted %p.ref, %.loc35_69.3 -// CHECK:STDOUT: return %.loc35_69.4 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseTuple(%p: DeriveFromTuple*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromTuple* = name_ref p, %p +// CHECK:STDOUT: %.loc59: ref DeriveFromTuple = deref %p.ref +// CHECK:STDOUT: %n.ref: = name_ref n, +// CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertToBadBaseStruct(%p: DeriveFromStruct*) -> {.a: i32, .b: i32}* { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: DeriveFromStruct* = name_ref p, %p -// CHECK:STDOUT: %.loc46_82.1: ref DeriveFromStruct = deref %p.ref -// CHECK:STDOUT: %.loc46_82.2: ref {.a: i32, .b: i32} = class_element_access %.loc46_82.1, element0 -// CHECK:STDOUT: %.loc46_82.3: {.a: i32, .b: i32}* = address_of %.loc46_82.2 -// CHECK:STDOUT: %.loc46_82.4: {.a: i32, .b: i32}* = converted %p.ref, %.loc46_82.3 -// CHECK:STDOUT: return %.loc46_82.4 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseStruct(%p: DeriveFromStruct*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromStruct* = name_ref p, %p +// CHECK:STDOUT: %.loc76: ref DeriveFromStruct = deref %p.ref +// CHECK:STDOUT: %n.ref: = name_ref n, +// CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertToBadBaseIncomplete(%p: DeriveFromIncomplete*) -> Incomplete* { @@ -226,13 +324,40 @@ fn ConvertToBadBaseFinal(p: DeriveFromFinal*) -> Final* { return p; } // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseIncomplete(%p: DeriveFromIncomplete*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromIncomplete* = name_ref p, %p +// CHECK:STDOUT: %.loc95: ref DeriveFromIncomplete = deref %p.ref +// CHECK:STDOUT: %n.ref: = name_ref n, +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: fn @ConvertToBadBaseFinal(%p: DeriveFromFinal*) -> Final* { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: DeriveFromFinal* = name_ref p, %p -// CHECK:STDOUT: %.loc72_67.1: ref DeriveFromFinal = deref %p.ref -// CHECK:STDOUT: %.loc72_67.2: ref Final = class_element_access %.loc72_67.1, element0 -// CHECK:STDOUT: %.loc72_67.3: Final* = address_of %.loc72_67.2 -// CHECK:STDOUT: %.loc72_67.4: Final* = converted %p.ref, %.loc72_67.3 -// CHECK:STDOUT: return %.loc72_67.4 +// CHECK:STDOUT: %.loc106_11.1: ref DeriveFromFinal = deref %p.ref +// CHECK:STDOUT: %.loc106_11.2: ref Final = class_element_access %.loc106_11.1, element0 +// CHECK:STDOUT: %.loc106_11.3: Final* = address_of %.loc106_11.2 +// CHECK:STDOUT: %.loc106_11.4: Final* = converted %p.ref, %.loc106_11.3 +// CHECK:STDOUT: return %.loc106_11.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseFinal_WithMember(%p: DeriveFromFinal*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromFinal* = name_ref p, %p +// CHECK:STDOUT: %.loc110_11: ref DeriveFromFinal = deref %p.ref +// CHECK:STDOUT: %.loc110_14.1: ref Final = class_element_access %.loc110_11, element0 +// CHECK:STDOUT: %.loc110_14.2: ref Final = converted %.loc110_11, %.loc110_14.1 +// CHECK:STDOUT: %.loc110_14.3: ref i32 = class_element_access %.loc110_14.2, element0 +// CHECK:STDOUT: %.loc110_14.4: i32 = bind_value %.loc110_14.3 +// CHECK:STDOUT: return %.loc110_14.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @AccessMemberWithInvalidBaseFinal_NoMember(%p: DeriveFromFinal*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: DeriveFromFinal* = name_ref p, %p +// CHECK:STDOUT: %.loc117: ref DeriveFromFinal = deref %p.ref +// CHECK:STDOUT: %b.ref: = name_ref b, +// CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_method_define.carbon b/toolchain/check/testdata/class/fail_base_method_define.carbon new file mode 100644 index 000000000000..db6fb70a196d --- /dev/null +++ b/toolchain/check/testdata/class/fail_base_method_define.carbon @@ -0,0 +1,87 @@ +// 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 + +base class B { + fn F(); + + class C { + fn F(); + } +} + +class D { + extend base: B; +} + +// TODO: This should be rejected. +fn D.F() {} + +// CHECK:STDERR: fail_base_method_define.carbon:[[@LINE+3]]:6: ERROR: Name `C` not found. +// CHECK:STDERR: fn D.C.F() {} +// CHECK:STDERR: ^ +fn D.C.F() {} + +// CHECK:STDOUT: --- fail_base_method_define.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc12_3.1: type = struct_type {} +// CHECK:STDOUT: %.loc12_3.2: type = tuple_type () +// CHECK:STDOUT: %.loc7: type = ptr_type {} +// CHECK:STDOUT: %.loc17: type = struct_type {.base: B} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace {.B = %B.decl, .D = %D.decl} +// CHECK:STDOUT: %B.decl = class_decl @B, () +// CHECK:STDOUT: %B: type = class_type @B +// CHECK:STDOUT: %D.decl = class_decl @D, () +// CHECK:STDOUT: %D: type = class_type @D +// CHECK:STDOUT: %F: = fn_decl @F.3 +// CHECK:STDOUT: %.loc25: = fn_decl @.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B { +// CHECK:STDOUT: %F: = fn_decl @F.1 +// CHECK:STDOUT: %C.decl = class_decl @C, () +// CHECK:STDOUT: %C: type = class_type @C +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %F: = fn_decl @F.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @D { +// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B +// CHECK:STDOUT: %.loc16_17.1: type = unbound_element_type D, B +// CHECK:STDOUT: %.loc16_17.2: = base_decl B, element0 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .base = %.loc16_17.2 +// CHECK:STDOUT: .F = file.%F +// CHECK:STDOUT: extend name_scope1 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.1(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.2(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F.3() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.1() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_modifiers.carbon b/toolchain/check/testdata/class/fail_base_modifiers.carbon index d52e3f550101..4cbf1aac3e37 100644 --- a/toolchain/check/testdata/class/fail_base_modifiers.carbon +++ b/toolchain/check/testdata/class/fail_base_modifiers.carbon @@ -78,6 +78,7 @@ class C4 { // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc13_25.2 +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @C2 { @@ -96,6 +97,7 @@ class C4 { // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc33_25.2 +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @C4 { @@ -105,5 +107,6 @@ class C4 { // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc43_24.2 +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_repeated.carbon b/toolchain/check/testdata/class/fail_base_repeated.carbon index 50e8aca11774..9558b4630e36 100644 --- a/toolchain/check/testdata/class/fail_base_repeated.carbon +++ b/toolchain/check/testdata/class/fail_base_repeated.carbon @@ -69,6 +69,7 @@ class D { // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc11_18.2 +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @D { @@ -79,5 +80,6 @@ class D { // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc23_18.2 +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_base_unbound.carbon b/toolchain/check/testdata/class/fail_base_unbound.carbon index c238ae0b0a9e..35ddf5c6dfea 100644 --- a/toolchain/check/testdata/class/fail_base_unbound.carbon +++ b/toolchain/check/testdata/class/fail_base_unbound.carbon @@ -48,5 +48,6 @@ let b: B = C.base; // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc10_17.2 +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/fail_derived_to_base.carbon b/toolchain/check/testdata/class/fail_derived_to_base.carbon index a8005175d33c..2c14a854825a 100644 --- a/toolchain/check/testdata/class/fail_derived_to_base.carbon +++ b/toolchain/check/testdata/class/fail_derived_to_base.carbon @@ -83,6 +83,7 @@ fn ConvertIncomplete(p: Incomplete*) -> A2* { return p; } // CHECK:STDOUT: !members: // CHECK:STDOUT: .base = %.loc16_18.2 // CHECK:STDOUT: .b = %b +// CHECK:STDOUT: extend name_scope2 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Incomplete; diff --git a/toolchain/check/testdata/class/self_conversion.carbon b/toolchain/check/testdata/class/self_conversion.carbon index c2f324a22e10..13f1ee02191d 100644 --- a/toolchain/check/testdata/class/self_conversion.carbon +++ b/toolchain/check/testdata/class/self_conversion.carbon @@ -70,6 +70,7 @@ fn Call(p: Derived*) -> i32 { // CHECK:STDOUT: .base = %.loc12_20.2 // CHECK:STDOUT: .SelfBase = %SelfBase // CHECK:STDOUT: .AddrSelfBase = %AddrSelfBase +// CHECK:STDOUT: extend name_scope1 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @SelfBase[%self: Base]() -> i32 { diff --git a/toolchain/check/testdata/packages/fail_api_not_found.carbon b/toolchain/check/testdata/packages/fail_api_not_found.carbon index 45f5a301ba5c..a8b009cfc3c6 100644 --- a/toolchain/check/testdata/packages/fail_api_not_found.carbon +++ b/toolchain/check/testdata/packages/fail_api_not_found.carbon @@ -28,18 +28,18 @@ library "Bar" impl; // CHECK:STDOUT: --- no_api.impl.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {has_load_error} +// CHECK:STDOUT: package: = namespace {has_error} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- no_api_lib.impl.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {has_load_error} +// CHECK:STDOUT: package: = namespace {has_error} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- no_api_main_lib.impl.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {has_load_error} +// CHECK:STDOUT: package: = namespace {has_error} // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/packages/fail_cycle.carbon b/toolchain/check/testdata/packages/fail_cycle.carbon index e0d0d70f99b6..7a073cb37ad2 100644 --- a/toolchain/check/testdata/packages/fail_cycle.carbon +++ b/toolchain/check/testdata/packages/fail_cycle.carbon @@ -74,7 +74,7 @@ import B; // CHECK:STDOUT: --- c.impl.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {has_load_error} +// CHECK:STDOUT: package: = namespace {has_error} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- cycle_child.carbon diff --git a/toolchain/check/testdata/packages/fail_name_with_import_failure.carbon b/toolchain/check/testdata/packages/fail_name_with_import_failure.carbon index 86daf27c7e3b..a4e188afc3b0 100644 --- a/toolchain/check/testdata/packages/fail_name_with_import_failure.carbon +++ b/toolchain/check/testdata/packages/fail_name_with_import_failure.carbon @@ -20,7 +20,7 @@ var a: () = A(); // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { -// CHECK:STDOUT: package: = namespace {.a = %a, has_load_error} +// CHECK:STDOUT: package: = namespace {.a = %a, has_error} // CHECK:STDOUT: %.loc7_9.1: () = tuple_literal () // CHECK:STDOUT: %.loc7_9.2: type = converted %.loc7_9.1, constants.%.loc7 // CHECK:STDOUT: %a.var: ref () = var a diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index a80362b61d22..e883dea966a1 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -174,6 +174,7 @@ CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer) CARBON_DIAGNOSTIC_KIND(DerefOfType) CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition) CARBON_DIAGNOSTIC_KIND(FunctionRedefinition) +CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend) CARBON_DIAGNOSTIC_KIND(NameNotFound) CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate) CARBON_DIAGNOSTIC_KIND(NameDeclPrevious) diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index fbb9bed99889..437576849b96 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -609,7 +609,7 @@ class Formatter { out_ << " {\n"; FormatCodeBlock(class_info.body_block_id); out_ << "\n!members:"; - FormatNameScope(class_info.scope_id, "", "\n ."); + FormatNameScope(class_info.scope_id, "", "\n "); out_ << "\n}\n"; } else { out_ << ";\n"; @@ -628,7 +628,7 @@ class Formatter { out_ << " {\n"; FormatCodeBlock(interface_info.body_block_id); out_ << "\n!members:"; - FormatNameScope(interface_info.scope_id, "", "\n ."); + FormatNameScope(interface_info.scope_id, "", "\n "); out_ << "\n}\n"; } else { out_ << ";\n"; @@ -721,14 +721,19 @@ class Formatter { llvm::ListSeparator sep(separator); for (auto [inst_id, name_id] : entries) { - out_ << sep << prefix; + out_ << sep << prefix << "."; FormatName(name_id); out_ << " = "; FormatInstName(inst_id); } - if (scope.has_load_error) { - out_ << sep << "has_load_error"; + for (auto extended_scope_id : scope.extended_scopes) { + // TODO: Print this scope in a better way. + out_ << sep << prefix << "extend " << extended_scope_id; + } + + if (scope.has_error) { + out_ << sep << prefix << "has_error"; } } @@ -986,7 +991,7 @@ class Formatter { auto FormatArg(NameScopeId id) -> void { out_ << '{'; - FormatNameScope(id, ", ", "."); + FormatNameScope(id, ", ", ""); out_ << '}'; } diff --git a/toolchain/sem_ir/value_stores.h b/toolchain/sem_ir/value_stores.h index f0c6ed2b40bb..2bd87e90a686 100644 --- a/toolchain/sem_ir/value_stores.h +++ b/toolchain/sem_ir/value_stores.h @@ -168,9 +168,30 @@ struct NameScope { // Names in the scope. llvm::DenseMap names; - // Whether the scope corresponds to an import that failed. There may still be - // names from successful imports, or the current file. - bool has_load_error = false; + // Scopes extended by this scope. + // + // TODO: A `NameScopeId` is currently insufficient to describe an extended + // scope in general. For example: + // + // class A(T:! type) { + // extend base: B(T*); + // } + // + // needs to describe the `T*` argument. + // + // Small vector size is set to 1: we expect that there will rarely be more + // than a single extended scope. Currently the only kind of extended scope is + // a base class, and there can be only one of those per scope. + // TODO: Revisit this once we have more kinds of extended scope and data. + // TODO: Consider using something like `TinyPtrVector` for this. + llvm::SmallVector extended_scopes; + + // Whether we have diagnosed an error in a construct that would have added + // names to this scope. For example, this can happen if an `import` failed or + // an `extend` declaration was ill-formed. If true, the `names` map is + // assumed to be missing names as a result of the error, and no further + // errors are produced for lookup failures in this scope. + bool has_error = false; }; // Provides a ValueStore wrapper for an API specific to name scopes.