diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index a5433892247a..b7aad978b87c 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -5291,57 +5291,58 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, class_decl->set_static_type(&self->static_type()); } - // Find base class declaration, if any. Verify that is before any data member - // declarations, and there is at most one. + // Find base class declaration, if any. Right now, verify that it is first in + // the class. This avoids the problem identified in + // https://github.com/carbon-language/carbon-lang/issues/2994 where the base + // class expression could reference an earlier declaration in the class that + // hasn't been typechecked yet and therefore doesn't have its `static_type` + // set. + + // TODO: Verify just that is before any data member declarations, and there is + // at most one, and delay remaining work (type checking, base class + // evaluation, etc.) until the `extend base` declaration is processed in + // order. std::optional> base_class; - bool after_data_member = false; - for (Nonnull m : class_decl->members()) { - switch (m->kind()) { - case DeclarationKind::VariableDeclaration: - case DeclarationKind::MixDeclaration: { - after_data_member = true; - break; + if (!class_decl->members().empty()) { + Nonnull m = class_decl->members()[0]; + if (m->kind() == DeclarationKind::ExtendBaseDeclaration) { + Nonnull base_class_expr = + cast(*m).base_class(); + CARBON_ASSIGN_OR_RETURN(const auto base_type, + TypeCheckTypeExp(base_class_expr, class_scope)); + if (base_type->kind() != Value::Kind::NominalClassType) { + return ProgramError(m->source_loc()) + << "Unsupported base class type for class `" + << class_decl->name() + << "`. Only simple classes are currently supported as base " + "class."; } - case DeclarationKind::ExtendBaseDeclaration: { - if (after_data_member) { - return ProgramError(m->source_loc()) - << "`extend base:` declaration must not be after `var` or " - "`mix` declarations in a class."; - } + CARBON_RETURN_IF_ERROR(ExpectCompleteType( + base_class_expr->source_loc(), "base class declaration", base_type)); + + base_class = cast(base_type); + if (base_class.value()->declaration().extensibility() == + ClassExtensibility::None) { + return ProgramError(m->source_loc()) + << "Base class `" << base_class.value()->declaration().name() + << "` is `final` and cannot be inherited. Add the `base` or " + "`abstract` class prefix to `" + << base_class.value()->declaration().name() + << "` to allow it to be inherited"; + } + class_decl->set_base_type(base_class); + } + for (Nonnull m : class_decl->members().drop_front()) { + if (m->kind() == DeclarationKind::ExtendBaseDeclaration) { if (base_class.has_value()) { return ProgramError(m->source_loc()) << "At most one `extend base:` declaration in a class."; - } - Nonnull base_class_expr = - cast(*m).base_class(); - CARBON_ASSIGN_OR_RETURN(const auto base_type, - TypeCheckTypeExp(base_class_expr, class_scope)); - if (base_type->kind() != Value::Kind::NominalClassType) { + } else { return ProgramError(m->source_loc()) - << "Unsupported base class type for class `" - << class_decl->name() - << "`. Only simple classes are currently supported as base " - "class."; + << "`extend base:` declarations after the first declaration " + "in the class are not yet supported"; } - CARBON_RETURN_IF_ERROR(ExpectCompleteType(base_class_expr->source_loc(), - "base class declaration", - base_type)); - - base_class = cast(base_type); - if (base_class.value()->declaration().extensibility() == - ClassExtensibility::None) { - return ProgramError(m->source_loc()) - << "Base class `" << base_class.value()->declaration().name() - << "` is `final` and cannot be inherited. Add the `base` or " - "`abstract` class prefix to `" - << base_class.value()->declaration().name() - << "` to allow it to be inherited"; - } - class_decl->set_base_type(base_class); - break; } - default: - break; } } diff --git a/explorer/testdata/class/fail_extend_after_var.carbon b/explorer/testdata/class/fail_extend_after_var.carbon index db4a31cd67b3..c577b03c8889 100644 --- a/explorer/testdata/class/fail_extend_after_var.carbon +++ b/explorer/testdata/class/fail_extend_after_var.carbon @@ -11,7 +11,7 @@ base class C { class E { var x: i32; - // CHECK:STDERR: COMPILATION ERROR: fail_extend_after_var.carbon:[[@LINE+1]]: `extend base:` declaration must not be after `var` or `mix` declarations in a class. + // CHECK:STDERR: COMPILATION ERROR: fail_extend_after_var.carbon:[[@LINE+1]]: `extend base:` declarations after the first declaration in the class are not yet supported extend base: C; } diff --git a/explorer/testdata/class/fail_extend_member_class.carbon b/explorer/testdata/class/fail_extend_member_class.carbon new file mode 100644 index 000000000000..3995702861ce --- /dev/null +++ b/explorer/testdata/class/fail_extend_member_class.carbon @@ -0,0 +1,22 @@ +// 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 + +package ExplorerTest api; + +class E { + base class C { + } + + // This should be allowed, but Explorer forbids it until the problem from + // https://github.com/carbon-language/carbon-lang/issues/2994 can be fixed. + + // CHECK:STDERR: COMPILATION ERROR: fail_extend_member_class.carbon:[[@LINE+1]]: `extend base:` declarations after the first declaration in the class are not yet supported + extend base: C; +} + +fn Main() -> i32 { + return 0; +}