Skip to content

Commit d491387

Browse files
dwblaikiezygoloid
andauthored
Disallow creating instances of abstract classes (#4381)
A good first-pass, at least. (abstract adapters are rejected with this change, though pending further language design discussion) --------- Co-authored-by: Richard Smith <[email protected]>
1 parent bafddd8 commit d491387

15 files changed

+865
-140
lines changed

toolchain/check/context.cpp

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id)
195195
emitter_->Emit(loc, NameNotFound, name_id);
196196
}
197197

198+
auto Context::NoteAbstractClass(SemIR::ClassId class_id,
199+
DiagnosticBuilder& builder) -> void {
200+
const auto& class_info = classes().Get(class_id);
201+
CARBON_CHECK(
202+
class_info.inheritance_kind == SemIR::Class::InheritanceKind::Abstract,
203+
"Class is not abstract");
204+
CARBON_DIAGNOSTIC(ClassAbstractHere, Note,
205+
"class was declared abstract here");
206+
builder.Note(class_info.definition_id, ClassAbstractHere);
207+
}
208+
198209
auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
199210
DiagnosticBuilder& builder) -> void {
200211
const auto& class_info = classes().Get(class_id);
@@ -1128,8 +1139,33 @@ class TypeCompleter {
11281139
} // namespace
11291140

11301141
auto Context::TryToCompleteType(SemIR::TypeId type_id,
1131-
BuildDiagnosticFn diagnoser) -> bool {
1132-
return TypeCompleter(*this, diagnoser).Complete(type_id);
1142+
BuildDiagnosticFn diagnoser,
1143+
BuildDiagnosticFn abstract_diagnoser) -> bool {
1144+
if (!TypeCompleter(*this, diagnoser).Complete(type_id)) {
1145+
return false;
1146+
}
1147+
1148+
if (!abstract_diagnoser) {
1149+
return true;
1150+
}
1151+
1152+
if (auto class_type = types().TryGetAs<SemIR::ClassType>(type_id)) {
1153+
auto& class_info = classes().Get(class_type->class_id);
1154+
if (class_info.inheritance_kind !=
1155+
SemIR::Class::InheritanceKind::Abstract) {
1156+
return true;
1157+
}
1158+
1159+
auto builder = abstract_diagnoser();
1160+
if (!builder) {
1161+
return false;
1162+
}
1163+
NoteAbstractClass(class_type->class_id, builder);
1164+
builder.Emit();
1165+
return false;
1166+
}
1167+
1168+
return true;
11331169
}
11341170

11351171
auto Context::TryToDefineType(SemIR::TypeId type_id,

toolchain/check/context.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ class Context {
235235
auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
236236
-> void;
237237

238+
// Adds a note to a diagnostic explaining that a class is abstract.
239+
auto NoteAbstractClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
240+
-> void;
241+
238242
// Adds a note to a diagnostic explaining that an interface is not defined.
239243
auto NoteUndefinedInterface(SemIR::InterfaceId interface_id,
240244
DiagnosticBuilder& builder) -> void;
@@ -319,7 +323,9 @@ class Context {
319323
// if a `diagnoser` is provided. The builder it returns will be annotated to
320324
// describe the reason why the type is not complete.
321325
auto TryToCompleteType(SemIR::TypeId type_id,
322-
BuildDiagnosticFn diagnoser = nullptr) -> bool;
326+
BuildDiagnosticFn diagnoser = nullptr,
327+
BuildDiagnosticFn abstract_diagnoser = nullptr)
328+
-> bool;
323329

324330
// Attempts to complete and define the type `type_id`. Returns `true` if the
325331
// type is defined, or `false` if no definition is available. A defined type
@@ -333,11 +339,12 @@ class Context {
333339
// Returns the type `type_id` as a complete type, or produces an incomplete
334340
// type error and returns an error type. This is a convenience wrapper around
335341
// TryToCompleteType. `diagnoser` must not be null.
336-
auto AsCompleteType(SemIR::TypeId type_id, BuildDiagnosticFn diagnoser)
342+
auto AsCompleteType(SemIR::TypeId type_id, BuildDiagnosticFn diagnoser,
343+
BuildDiagnosticFn abstract_diagnoser = nullptr)
337344
-> SemIR::TypeId {
338-
CARBON_CHECK(diagnoser);
339-
return TryToCompleteType(type_id, diagnoser) ? type_id
340-
: SemIR::TypeId::Error;
345+
return TryToCompleteType(type_id, diagnoser, abstract_diagnoser)
346+
? type_id
347+
: SemIR::TypeId::Error;
341348
}
342349

343350
// Returns whether `type_id` represents a facet type.

toolchain/check/convert.cpp

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -533,15 +533,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
533533
ConversionTarget target) -> SemIR::InstId {
534534
PendingBlock target_block(context);
535535
auto& dest_class_info = context.classes().Get(dest_type.class_id);
536-
if (dest_class_info.inheritance_kind == SemIR::Class::Abstract) {
537-
CARBON_DIAGNOSTIC(ConstructionOfAbstractClass, Error,
538-
"cannot construct instance of abstract class; "
539-
"consider using `partial {0}` instead",
540-
TypeIdAsRawType);
541-
context.emitter().Emit(value_id, ConstructionOfAbstractClass,
542-
target.type_id);
543-
return SemIR::InstId::BuiltinError;
544-
}
536+
CARBON_CHECK(dest_class_info.inheritance_kind != SemIR::Class::Abstract);
545537
auto object_repr_id =
546538
dest_class_info.GetObjectRepr(context.sem_ir(), dest_type.specific_id);
547539
if (object_repr_id == SemIR::TypeId::Error) {
@@ -937,23 +929,38 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
937929
}
938930

939931
// We can only perform initialization for complete types.
940-
if (!context.TryToCompleteType(target.type_id, [&] {
941-
CARBON_DIAGNOSTIC(IncompleteTypeInInit, Error,
942-
"initialization of incomplete type {0}",
943-
SemIR::TypeId);
944-
CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
945-
"forming value of incomplete type {0}",
946-
SemIR::TypeId);
947-
CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
948-
"invalid use of incomplete type {0}", SemIR::TypeId);
949-
return context.emitter().Build(loc_id,
950-
target.is_initializer()
951-
? IncompleteTypeInInit
952-
: target.kind == ConversionTarget::Value
953-
? IncompleteTypeInValueConversion
954-
: IncompleteTypeInConversion,
955-
target.type_id);
956-
})) {
932+
if (!context.TryToCompleteType(
933+
target.type_id,
934+
[&] {
935+
CARBON_DIAGNOSTIC(IncompleteTypeInInit, Error,
936+
"initialization of incomplete type {0}",
937+
SemIR::TypeId);
938+
CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
939+
"forming value of incomplete type {0}",
940+
SemIR::TypeId);
941+
CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
942+
"invalid use of incomplete type {0}",
943+
SemIR::TypeId);
944+
assert(!target.is_initializer());
945+
assert(target.kind == ConversionTarget::Value);
946+
return context.emitter().Build(
947+
loc_id,
948+
target.is_initializer() ? IncompleteTypeInInit
949+
: target.kind == ConversionTarget::Value
950+
? IncompleteTypeInValueConversion
951+
: IncompleteTypeInConversion,
952+
target.type_id);
953+
},
954+
[&] {
955+
CARBON_DIAGNOSTIC(AbstractTypeInInit, Error,
956+
"initialization of abstract type {0}",
957+
SemIR::TypeId);
958+
if (!target.is_initializer()) {
959+
return context.emitter().BuildSuppressed();
960+
}
961+
return context.emitter().Build(loc_id, AbstractTypeInInit,
962+
target.type_id);
963+
})) {
957964
return SemIR::InstId::BuiltinError;
958965
}
959966

toolchain/check/function.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,18 @@ auto CheckFunctionReturnType(Context& context, SemIRLoc loc,
8181
return context.emitter().Build(loc, IncompleteTypeInFunctionReturnType,
8282
return_info.type_id);
8383
};
84+
auto diagnose_abstract_return_type = [&] {
85+
CARBON_DIAGNOSTIC(AbstractTypeInFunctionReturnType, Error,
86+
"function returns abstract type {0}", SemIR::TypeId);
87+
return context.emitter().Build(loc, AbstractTypeInFunctionReturnType,
88+
return_info.type_id);
89+
};
8490

8591
// TODO: Consider suppressing the diagnostic if we've already diagnosed a
8692
// definition or call to this function.
8793
if (context.TryToCompleteType(return_info.type_id,
88-
diagnose_incomplete_return_type)) {
94+
diagnose_incomplete_return_type,
95+
diagnose_abstract_return_type)) {
8996
return_info = SemIR::ReturnTypeInfo::ForFunction(context.sem_ir(),
9097
function, specific_id);
9198
}

toolchain/check/handle_binding_pattern.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,28 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
100100

101101
// A `var` declaration at class scope introduces a field.
102102
auto parent_class_decl = context.GetCurrentScopeAs<SemIR::ClassDecl>();
103-
cast_type_id = context.AsCompleteType(cast_type_id, [&] {
104-
CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error,
105-
"{0} has incomplete type {1}", llvm::StringLiteral,
106-
InstIdAsType);
107-
return context.emitter().Build(type_node, IncompleteTypeInVarDecl,
108-
parent_class_decl
109-
? llvm::StringLiteral("Field")
110-
: llvm::StringLiteral("Variable"),
111-
cast_type_inst_id);
112-
});
103+
cast_type_id = context.AsCompleteType(
104+
cast_type_id,
105+
[&] {
106+
CARBON_DIAGNOSTIC(IncompleteTypeInVarDecl, Error,
107+
"{0} has incomplete type {1}",
108+
llvm::StringLiteral, SemIR::TypeId);
109+
return context.emitter().Build(
110+
type_node, IncompleteTypeInVarDecl,
111+
parent_class_decl ? llvm::StringLiteral("field")
112+
: llvm::StringLiteral("variable"),
113+
cast_type_id);
114+
},
115+
[&] {
116+
CARBON_DIAGNOSTIC(AbstractTypeInVarDecl, Error,
117+
"{0} has abstract type {1}", llvm::StringLiteral,
118+
SemIR::TypeId);
119+
return context.emitter().Build(
120+
type_node, AbstractTypeInVarDecl,
121+
parent_class_decl ? llvm::StringLiteral("field")
122+
: llvm::StringLiteral("variable"),
123+
cast_type_id);
124+
});
113125
if (parent_class_decl) {
114126
CARBON_CHECK(context_node_kind == Parse::NodeKind::VariableIntroducer,
115127
"`returned var` at class scope");

toolchain/check/handle_class.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -383,14 +383,24 @@ auto HandleParseNode(Context& context, Parse::AdaptDeclId node_id) -> bool {
383383
return true;
384384
}
385385

386-
auto [adapted_type_inst_id, adapted_type_id] =
387-
ExprAsType(context, node_id, adapted_type_expr_id);
388-
adapted_type_id = context.AsCompleteType(adapted_type_id, [&] {
389-
CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error,
390-
"adapted type {0} is an incomplete type", InstIdAsType);
391-
return context.emitter().Build(node_id, IncompleteTypeInAdaptDecl,
392-
adapted_type_inst_id);
393-
});
386+
auto adapted_type_id =
387+
ExprAsType(context, node_id, adapted_type_expr_id).type_id;
388+
adapted_type_id = context.AsCompleteType(
389+
adapted_type_id,
390+
[&] {
391+
CARBON_DIAGNOSTIC(IncompleteTypeInAdaptDecl, Error,
392+
"adapted type {0} is an incomplete type",
393+
SemIR::TypeId);
394+
return context.emitter().Build(node_id, IncompleteTypeInAdaptDecl,
395+
adapted_type_id);
396+
},
397+
[&] {
398+
CARBON_DIAGNOSTIC(AbstractTypeInAdaptDecl, Error,
399+
"adapted type {0} is an abstract type",
400+
SemIR::TypeId);
401+
return context.emitter().Build(node_id, AbstractTypeInAdaptDecl,
402+
adapted_type_id);
403+
});
394404

395405
// Build a SemIR representation for the declaration.
396406
class_info.adapt_id = context.AddInst<SemIR::AdaptDecl>(

toolchain/check/testdata/array/fail_incomplete_element.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class Incomplete;
1212

13-
// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+6]]:8: error: Variable has incomplete type `[Incomplete; 1]`
13+
// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+6]]:8: error: variable has incomplete type `[Incomplete; 1]`
1414
// CHECK:STDERR: var a: [Incomplete; 1];
1515
// CHECK:STDERR: ^~~~~~~~~~~~~~~
1616
// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE-5]]:1: note: class was forward declared here

toolchain/check/testdata/class/cross_package_import.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ library "[[@TEST_NAME]]";
4848

4949
import Other library "other_extern";
5050

51-
// CHECK:STDERR: fail_extern.carbon:[[@LINE+8]]:8: error: Variable has incomplete type `C`
51+
// CHECK:STDERR: fail_extern.carbon:[[@LINE+8]]:8: error: variable has incomplete type `C`
5252
// CHECK:STDERR: var c: Other.C = {};
5353
// CHECK:STDERR: ^~~~~~~
5454
// CHECK:STDERR: fail_extern.carbon:[[@LINE-5]]:1: in import

0 commit comments

Comments
 (0)