mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Basic lowering support for classes. (#3334)
Track the fields in a class, and generate a corresponding struct type as the object representation for the class. For now, we always use a pointer as the value representation for a class.
This commit is contained in:
+31
-12
@@ -92,9 +92,17 @@ auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
|
||||
DiagnosticBuilder& builder) -> void {
|
||||
CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note,
|
||||
"Class was forward declared here.");
|
||||
CARBON_DIAGNOSTIC(ClassIncompleteWithinDefinition, Note,
|
||||
"Class is incomplete within its definition.");
|
||||
const auto& class_info = classes().Get(class_id);
|
||||
builder.Note(nodes().Get(class_info.declaration_id).parse_node(),
|
||||
ClassForwardDeclaredHere);
|
||||
CARBON_CHECK(!class_info.is_defined()) << "Class is not incomplete";
|
||||
if (class_info.definition_id.is_valid()) {
|
||||
builder.Note(nodes().Get(class_info.definition_id).parse_node(),
|
||||
ClassIncompleteWithinDefinition);
|
||||
} else {
|
||||
builder.Note(nodes().Get(class_info.declaration_id).parse_node(),
|
||||
ClassForwardDeclaredHere);
|
||||
}
|
||||
}
|
||||
|
||||
auto Context::AddNameToLookup(Parse::Node name_node, StringId name_id,
|
||||
@@ -410,15 +418,20 @@ class TypeCompleter {
|
||||
}
|
||||
break;
|
||||
|
||||
case SemIR::ClassType::Kind:
|
||||
// TODO: Support class definitions and complete class types.
|
||||
if (diagnoser_) {
|
||||
auto builder = (*diagnoser_)();
|
||||
context_.NoteIncompleteClass(
|
||||
type_node.As<SemIR::ClassType>().class_id, builder);
|
||||
builder.Emit();
|
||||
case SemIR::ClassType::Kind: {
|
||||
auto class_type = type_node.As<SemIR::ClassType>();
|
||||
auto& class_info = context_.classes().Get(class_type.class_id);
|
||||
if (!class_info.is_defined()) {
|
||||
if (diagnoser_) {
|
||||
auto builder = (*diagnoser_)();
|
||||
context_.NoteIncompleteClass(class_type.class_id, builder);
|
||||
builder.Emit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
Push(class_info.object_representation_id);
|
||||
break;
|
||||
}
|
||||
|
||||
case SemIR::ConstType::Kind:
|
||||
Push(type_node.As<SemIR::ConstType>().inner_id);
|
||||
@@ -659,8 +672,14 @@ class TypeCompleter {
|
||||
node.As<SemIR::TupleType>());
|
||||
|
||||
case SemIR::ClassType::Kind:
|
||||
// TODO: Support class definitions and complete class types.
|
||||
CARBON_FATAL() << "Class types are currently never complete";
|
||||
// The value representation for a class is a pointer to the object
|
||||
// representation.
|
||||
// TODO: Support customized value representations for classes.
|
||||
// TODO: Pick a better value representation when possible.
|
||||
return MakePointerRepresentation(
|
||||
node.parse_node(), context_.classes()
|
||||
.Get(node.As<SemIR::ClassType>().class_id)
|
||||
.object_representation_id);
|
||||
|
||||
case SemIR::Builtin::Kind:
|
||||
CARBON_FATAL() << "Builtins should be named as cross-references";
|
||||
|
||||
@@ -126,8 +126,7 @@ auto DeclarationNameStack::UpdateScopeIfNeeded(NameContext& name_context)
|
||||
case SemIR::ClassDeclaration::Kind: {
|
||||
const auto& class_info = context_->classes().Get(
|
||||
resolved_node.As<SemIR::ClassDeclaration>().class_id);
|
||||
// TODO: Check that the class is complete rather than that it has a scope.
|
||||
if (class_info.scope_id.is_valid()) {
|
||||
if (class_info.is_defined()) {
|
||||
name_context.state = NameContext::State::Resolved;
|
||||
name_context.target_scope_id = class_info.scope_id;
|
||||
} else {
|
||||
|
||||
@@ -106,6 +106,7 @@ auto HandleClassDefinitionStart(Context& context, Parse::Node parse_node)
|
||||
context.PushScope(class_decl_id, class_info.scope_id);
|
||||
context.node_block_stack().Push();
|
||||
context.node_stack().Push(parse_node, class_id);
|
||||
context.args_type_info_stack().Push();
|
||||
|
||||
// TODO: Handle the case where there's control flow in the class body. For
|
||||
// example:
|
||||
@@ -119,13 +120,17 @@ auto HandleClassDefinitionStart(Context& context, Parse::Node parse_node)
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleClassDefinition(Context& context, Parse::Node /*parse_node*/)
|
||||
-> bool {
|
||||
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
|
||||
auto HandleClassDefinition(Context& context, Parse::Node parse_node) -> bool {
|
||||
auto fields_id = context.args_type_info_stack().Pop();
|
||||
auto class_id =
|
||||
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
|
||||
context.node_block_stack().Pop();
|
||||
context.PopScope();
|
||||
|
||||
// TODO: Mark the class as a complete type.
|
||||
// The class type is now fully defined.
|
||||
auto& class_info = context.classes().Get(class_id);
|
||||
class_info.object_representation_id =
|
||||
context.CanonicalizeStructType(parse_node, fields_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id)
|
||||
}
|
||||
if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
|
||||
auto& class_info = context.classes().Get(base_as_class->class_id);
|
||||
if (!class_info.scope_id.is_valid()) {
|
||||
if (!class_info.is_defined()) {
|
||||
CARBON_DIAGNOSTIC(QualifiedExpressionInIncompleteClassScope, Error,
|
||||
"Member access into incomplete class `{0}`.",
|
||||
std::string);
|
||||
|
||||
@@ -57,6 +57,10 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
|
||||
value_type_id = context.CanonicalizeType(field_type_node_id);
|
||||
value_id =
|
||||
context.AddNode(SemIR::Field{parse_node, value_type_id, name_id});
|
||||
|
||||
// Add a corresponding field to the object representation of the class.
|
||||
context.args_type_info_stack().AddNode(
|
||||
SemIR::StructTypeField{parse_node, name_id, cast_type_id});
|
||||
} else {
|
||||
value_id = context.AddNode(
|
||||
SemIR::VarStorage{name_node, value_type_id, name_id});
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ fn Run() -> i32 {
|
||||
// CHECK:STDOUT: file "basic.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc15: type = struct_type {.k: i32}
|
||||
// CHECK:STDOUT: %G: <function> = fn_decl @G
|
||||
// CHECK:STDOUT: %Run: <function> = fn_decl @Run
|
||||
// CHECK:STDOUT: }
|
||||
|
||||
@@ -24,6 +24,7 @@ fn T.F() {}
|
||||
// CHECK:STDOUT: file "fail_member_of_let.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc9: type = struct_type {}
|
||||
// CHECK:STDOUT: %Class.ref: type = name_reference "Class", %Class
|
||||
// CHECK:STDOUT: %T: type = bind_name "T", %Class.ref
|
||||
// CHECK:STDOUT: %.loc22: <function> = fn_decl @.1
|
||||
|
||||
@@ -27,6 +27,7 @@ fn Class.H() {}
|
||||
// CHECK:STDOUT: file "fail_redefinition.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc10: type = struct_type {}
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %F: <function> = fn_decl @F
|
||||
// CHECK:STDOUT: %G: <function> = fn_decl @G
|
||||
|
||||
+9
-2
@@ -8,6 +8,12 @@ class Class {
|
||||
fn G() -> i32 {
|
||||
// TODO: This should find the member function `F` even though it's declared
|
||||
// later.
|
||||
// CHECK:STDERR: fail_reorder.carbon:[[@LINE+12]]:12: ERROR: Member access into incomplete class `Class`.
|
||||
// CHECK:STDERR: return Class.F();
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR: fail_reorder.carbon:[[@LINE-7]]:1: Class is incomplete within its definition.
|
||||
// CHECK:STDERR: class Class {
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR: fail_reorder.carbon:[[@LINE+6]]:17: ERROR: Name `F` not found.
|
||||
// CHECK:STDERR: return Class.F();
|
||||
// CHECK:STDERR: ^
|
||||
@@ -25,6 +31,7 @@ class Class {
|
||||
// CHECK:STDOUT: file "fail_reorder.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc29: type = struct_type {}
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: class @Class {
|
||||
@@ -45,6 +52,6 @@ class Class {
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @F() -> i32 {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %.loc21: i32 = int_literal 1
|
||||
// CHECK:STDOUT: return %.loc21
|
||||
// CHECK:STDOUT: %.loc27: i32 = int_literal 1
|
||||
// CHECK:STDOUT: return %.loc27
|
||||
// CHECK:STDOUT: }
|
||||
|
||||
@@ -24,6 +24,7 @@ class Class {
|
||||
// CHECK:STDOUT: file "fail_scope.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc22: type = struct_type {}
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: class @Class {
|
||||
|
||||
@@ -25,6 +25,7 @@ fn G() -> i32 {
|
||||
// CHECK:STDOUT: file "fail_unbound_field.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc16: type = struct_type {.field: i32}
|
||||
// CHECK:STDOUT: %G: <function> = fn_decl @G
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
|
||||
@@ -16,6 +16,7 @@ fn Class.F() {}
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %.loc11: type = struct_type {}
|
||||
// CHECK:STDOUT: %F: <function> = fn_decl @F
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ fn Run() -> i32 {
|
||||
// CHECK:STDOUT: file "scope.carbon" {
|
||||
// CHECK:STDOUT: class_declaration @Class, ()
|
||||
// CHECK:STDOUT: %Class: type = class_type @Class
|
||||
// CHECK:STDOUT: %.loc11: type = struct_type {}
|
||||
// CHECK:STDOUT: %F: <function> = fn_decl @F.2
|
||||
// CHECK:STDOUT: %Run: <function> = fn_decl @Run
|
||||
// CHECK:STDOUT: }
|
||||
|
||||
@@ -118,6 +118,7 @@ CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
|
||||
CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
|
||||
CARBON_DIAGNOSTIC_KIND(ClassPreviousDefinition)
|
||||
CARBON_DIAGNOSTIC_KIND(ClassRedefinition)
|
||||
CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
|
||||
CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
|
||||
CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer)
|
||||
CARBON_DIAGNOSTIC_KIND(DereferenceOfType)
|
||||
|
||||
@@ -225,6 +225,13 @@ auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* {
|
||||
GetType(array_type.element_type_id),
|
||||
sem_ir_->GetArrayBoundValue(array_type.bound_id));
|
||||
}
|
||||
case SemIR::ClassType::Kind: {
|
||||
auto object_representation_id =
|
||||
sem_ir_->classes()
|
||||
.Get(node.As<SemIR::ClassType>().class_id)
|
||||
.object_representation_id;
|
||||
return GetType(object_representation_id);
|
||||
}
|
||||
case SemIR::ConstType::Kind:
|
||||
return GetType(node.As<SemIR::ConstType>().inner_id);
|
||||
case SemIR::PointerType::Kind:
|
||||
@@ -258,6 +265,10 @@ auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* {
|
||||
}
|
||||
return llvm::StructType::get(*llvm_context_, subtypes);
|
||||
}
|
||||
case SemIR::UnboundFieldType::Kind: {
|
||||
// Return an empty struct as a placeholder.
|
||||
return llvm::StructType::get(*llvm_context_);
|
||||
}
|
||||
default: {
|
||||
CARBON_FATAL() << "Cannot use node as type: " << node_id << " " << node;
|
||||
}
|
||||
|
||||
@@ -210,6 +210,8 @@ auto HandleNameReference(FunctionContext& context, SemIR::NodeId node_id,
|
||||
auto target = context.sem_ir().nodes().Get(node.value_id);
|
||||
if (auto function_decl = target.TryAs<SemIR::FunctionDeclaration>()) {
|
||||
context.SetLocal(node_id, context.GetFunction(function_decl->function_id));
|
||||
} else if (auto class_type = target.TryAs<SemIR::ClassType>()) {
|
||||
context.SetLocal(node_id, context.GetTypeAsValue());
|
||||
} else {
|
||||
// TODO: Handle other kinds of name references to globals.
|
||||
context.SetLocal(node_id, context.GetLocal(node.value_id));
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// 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
|
||||
|
||||
class C {
|
||||
var a: i32;
|
||||
var b: C*;
|
||||
}
|
||||
|
||||
fn F(c: C) -> C {
|
||||
// TODO: We should copy `c` into the return slot here.
|
||||
return c;
|
||||
}
|
||||
|
||||
fn Run() {
|
||||
var c: C;
|
||||
var d: C = F(c);
|
||||
}
|
||||
|
||||
// CHECK:STDOUT: ; ModuleID = 'basic.carbon'
|
||||
// CHECK:STDOUT: source_filename = "basic.carbon"
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: define void @F(ptr sret({ i32, ptr }) %return, ptr %c) {
|
||||
// CHECK:STDOUT: ret void
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: define void @main() {
|
||||
// CHECK:STDOUT: %c = alloca { i32, ptr }, align 8
|
||||
// CHECK:STDOUT: %d = alloca { i32, ptr }, align 8
|
||||
// CHECK:STDOUT: call void @F(ptr %d, ptr %c)
|
||||
// CHECK:STDOUT: ret void
|
||||
// CHECK:STDOUT: }
|
||||
+17
-2
@@ -62,20 +62,35 @@ struct Class : public Printable<Class> {
|
||||
out << "}";
|
||||
}
|
||||
|
||||
// Determines whether this class has been fully defined. This is false until
|
||||
// we reach the `}` of the class definition.
|
||||
bool is_defined() const { return object_representation_id.is_valid(); }
|
||||
|
||||
// The following members always have values, and do not change throughout the
|
||||
// lifetime of the class.
|
||||
|
||||
// The class name.
|
||||
StringId name_id;
|
||||
// The class type, which is the type of `Self` in the class definition.
|
||||
TypeId self_type_id;
|
||||
// The first declaration of the class. This is a ClassDeclaration.
|
||||
NodeId declaration_id = NodeId::Invalid;
|
||||
// The definition, if the class has been defined or is currently being
|
||||
// defined. This is a ClassDeclaration.
|
||||
|
||||
// The following members are set at the `{` of the class definition.
|
||||
|
||||
// The definition of the class. This is a ClassDeclaration.
|
||||
NodeId definition_id = NodeId::Invalid;
|
||||
// The class scope.
|
||||
NameScopeId scope_id = NameScopeId::Invalid;
|
||||
// The first block of the class body.
|
||||
// TODO: Handle control flow in the class body, such as if-expressions.
|
||||
NodeBlockId body_block_id = NodeBlockId::Invalid;
|
||||
|
||||
// The following members are set at the `}` of the class definition.
|
||||
|
||||
// The object representation type to use for this class. This is valid once
|
||||
// the class is defined.
|
||||
TypeId object_representation_id = TypeId::Invalid;
|
||||
};
|
||||
|
||||
// The value representation to use when passing by value.
|
||||
|
||||
Reference in New Issue
Block a user