Skip to content

Commit

Permalink
Add interface support to check (carbon-language#3474)
Browse files Browse the repository at this point in the history
Largely copied from the `class` code
  • Loading branch information
josh11b committed Dec 8, 2023
1 parent 2705a32 commit 3b0923c
Show file tree
Hide file tree
Showing 21 changed files with 677 additions and 18 deletions.
1 change: 1 addition & 0 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ class TypeCompleter {
case SemIR::FunctionDecl::Kind:
case SemIR::Import::Kind:
case SemIR::InitializeFrom::Kind:
case SemIR::InterfaceDecl::Kind:
case SemIR::IntLiteral::Kind:
case SemIR::NameRef::Kind:
case SemIR::Namespace::Kind:
Expand Down
12 changes: 8 additions & 4 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,14 @@ class Context {
// Returns true if currently at file scope.
auto at_file_scope() const -> bool { return scope_stack_.size() == 1; }

// Returns the instruction kind associated with the current scope, if any.
auto current_scope_kind() const -> std::optional<SemIR::InstKind> {
// Returns true if the current scope is of the specified kind.
template <typename InstT>
auto CurrentScopeIs() -> bool {
auto current_scope_inst_id = current_scope().scope_inst_id;
if (!current_scope_inst_id.is_valid()) {
return std::nullopt;
return false;
}
return sem_ir_->insts().Get(current_scope_inst_id).kind();
return sem_ir_->insts().Get(current_scope_inst_id).kind() == InstT::Kind;
}

// Returns the current scope, if it is of the specified kind. Otherwise,
Expand Down Expand Up @@ -336,6 +337,9 @@ class Context {
return sem_ir().functions();
}
auto classes() -> ValueStore<SemIR::ClassId>& { return sem_ir().classes(); }
auto interfaces() -> ValueStore<SemIR::InterfaceId>& {
return sem_ir().interfaces();
}
auto cross_ref_irs() -> ValueStore<SemIR::CrossRefIRId>& {
return sem_ir().cross_ref_irs();
}
Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ static auto BuildFunctionDecl(Context& context, bool is_definition)
"method modifier");
}
if (!!(modifiers & KeywordModifierSet::Interface)) {
// TODO: Once we are saving the modifiers for a function, add check that
// the function may only be defined if it is marked `default` or `final`.
context.TODO(context.decl_state_stack().innermost().saw_decl_modifier,
"interface modifier");
}
Expand Down
139 changes: 132 additions & 7 deletions toolchain/check/handle_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,151 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "toolchain/check/context.h"
#include "toolchain/check/modifiers.h"

namespace Carbon::Check {

auto HandleInterfaceDecl(Context& context, Parse::NodeId parse_node) -> bool {
return context.TODO(parse_node, "HandleInterfaceDecl");
auto HandleInterfaceIntroducer(Context& context, Parse::NodeId parse_node)
-> bool {
// Create an instruction block to hold the instructions created as part of the
// interface signature, such as generic parameters.
context.inst_block_stack().Push();
// Push the bracketing node.
context.node_stack().Push(parse_node);
// Optional modifiers and the name follow.
context.decl_state_stack().Push(DeclState::Interface, parse_node);
context.decl_name_stack().PushScopeAndStartName();
return true;
}

static auto BuildInterfaceDecl(Context& context)
-> std::tuple<SemIR::InterfaceId, SemIR::InstId> {
auto name_context = context.decl_name_stack().FinishName();
context.node_stack()
.PopAndDiscardSoloParseNode<Parse::NodeKind::InterfaceIntroducer>();
auto first_node = context.decl_state_stack().innermost().first_node;

// Process modifiers.
CheckAccessModifiersOnDecl(context, Lex::TokenKind::Interface);
LimitModifiersOnDecl(context, KeywordModifierSet::Access,
Lex::TokenKind::Interface);

auto modifiers = context.decl_state_stack().innermost().modifier_set;
if (!!(modifiers & KeywordModifierSet::Access)) {
context.TODO(context.decl_state_stack().innermost().saw_access_modifier,
"access modifier");
}

auto decl_block_id = context.inst_block_stack().Pop();

// Add the interface declaration.
auto interface_decl = SemIR::InterfaceDecl{
first_node, SemIR::InterfaceId::Invalid, decl_block_id};
auto interface_decl_id = context.AddInst(interface_decl);

// Check whether this is a redeclaration.
auto existing_id = context.decl_name_stack().LookupOrAddName(
name_context, interface_decl_id);
if (existing_id.is_valid()) {
if (auto existing_interface_decl =
context.insts().Get(existing_id).TryAs<SemIR::InterfaceDecl>()) {
// This is a redeclaration of an existing interface.
interface_decl.interface_id = existing_interface_decl->interface_id;

// TODO: Check that the generic parameter list agrees with the prior
// declaration.
} else {
// This is a redeclaration of something other than a interface.
context.DiagnoseDuplicateName(name_context.parse_node, existing_id);
}
}

// Create a new interface if this isn't a valid redeclaration.
if (!interface_decl.interface_id.is_valid()) {
// TODO: If this is an invalid redeclaration of a non-interface entity or
// there was an error in the qualifier, we will have lost track of the
// interface name here. We should keep track of it even if the name is
// invalid.
// TODO: should have a `Self` type id member
interface_decl.interface_id = context.interfaces().Add(
{.name_id =
name_context.state == DeclNameStack::NameContext::State::Unresolved
? name_context.unresolved_name_id
: SemIR::NameId::Invalid,
.decl_id = interface_decl_id});
}

// Write the interface ID into the InterfaceDecl.
context.insts().Set(interface_decl_id, interface_decl);

return {interface_decl.interface_id, interface_decl_id};
}

auto HandleInterfaceDefinition(Context& context, Parse::NodeId parse_node)
auto HandleInterfaceDecl(Context& context, Parse::NodeId /*parse_node*/)
-> bool {
return context.TODO(parse_node, "HandleInterfaceDefinition");
BuildInterfaceDecl(context);
context.decl_name_stack().PopScope();
context.decl_state_stack().Pop(DeclState::Interface);
return true;
}

auto HandleInterfaceDefinitionStart(Context& context, Parse::NodeId parse_node)
-> bool {
return context.TODO(parse_node, "HandleInterfaceDefinitionStart");
auto [interface_id, interface_decl_id] = BuildInterfaceDecl(context);
auto& interface_info = context.interfaces().Get(interface_id);

// Track that this declaration is the definition.
if (interface_info.definition_id.is_valid()) {
CARBON_DIAGNOSTIC(InterfaceRedefinition, Error,
"Redefinition of interface {0}.", std::string);
CARBON_DIAGNOSTIC(InterfacePreviousDefinition, Note,
"Previous definition was here.");
context.emitter()
.Build(parse_node, InterfaceRedefinition,
context.names().GetFormatted(interface_info.name_id).str())
.Note(context.insts().Get(interface_info.definition_id).parse_node(),
InterfacePreviousDefinition)
.Emit();
} else {
interface_info.definition_id = interface_decl_id;
interface_info.scope_id = context.name_scopes().Add();
}

// Enter the interface scope.
context.PushScope(interface_decl_id, interface_info.scope_id);

// TODO: Introduce `Self`.

context.inst_block_stack().Push();
context.node_stack().Push(parse_node, interface_id);
// TODO: Perhaps use the args_type_info_stack for a witness table.

// TODO: Handle the case where there's control flow in the interface body. For
// example:
//
// interface C {
// let v: if true then i32 else f64;
// }
//
// We may need to track a list of instruction blocks here, as we do for a
// function.
interface_info.body_block_id = context.inst_block_stack().PeekOrAdd();
return true;
}

auto HandleInterfaceIntroducer(Context& context, Parse::NodeId parse_node)
auto HandleInterfaceDefinition(Context& context, Parse::NodeId /*parse_node*/)
-> bool {
return context.TODO(parse_node, "HandleInterfaceIntroducer");
auto interface_id =
context.node_stack().Pop<Parse::NodeKind::InterfaceDefinitionStart>();
context.inst_block_stack().Pop();
context.PopScope();
context.decl_name_stack().PopScope();
context.decl_state_stack().Pop(DeclState::Interface);

// The interface type is now fully defined.
auto& interface_info = context.interfaces().Get(interface_id);
interface_info.defined = true;
return true;
}

} // namespace Carbon::Check
13 changes: 7 additions & 6 deletions toolchain/check/modifiers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,9 @@ auto CheckAccessModifiersOnDecl(Context& context, Lex::TokenKind decl_kind)
return;
}

if (auto kind = context.current_scope_kind()) {
if (*kind == SemIR::ClassDecl::Kind) {
// Both `private` and `protected` allowed in a class definition.
return;
}
if (context.CurrentScopeIs<SemIR::ClassDecl>()) {
// Both `private` and `protected` allowed in a class definition.
return;
}

// Otherwise neither `private` nor `protected` allowed.
Expand All @@ -88,7 +86,10 @@ auto CheckAccessModifiersOnDecl(Context& context, Lex::TokenKind decl_kind)

auto RequireDefaultFinalOnlyInInterfaces(Context& context,
Lex::TokenKind decl_kind) -> void {
// TODO: Skip this if *context.current_scope_kind() == SemIR::InterfaceDecl
if (context.CurrentScopeIs<SemIR::InterfaceDecl>()) {
// Both `default` and `final` allowed in an interface definition.
return;
}
ForbidModifiersOnDecl(context, KeywordModifierSet::Interface, decl_kind,
" outside of an interface");
}
Expand Down
21 changes: 21 additions & 0 deletions toolchain/check/node_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ class NodeStack {
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
if constexpr (RequiredIdKind == IdKind::InterfaceId) {
auto back = PopWithParseNode<SemIR::InterfaceId>();
RequireParseKind<RequiredParseKind>(back.first);
return back;
}
if constexpr (RequiredIdKind == IdKind::NameId) {
auto back = PopWithParseNode<SemIR::NameId>();
RequireParseKind<RequiredParseKind>(back.first);
Expand Down Expand Up @@ -215,6 +220,9 @@ class NodeStack {
if constexpr (RequiredIdKind == IdKind::ClassId) {
return back.id<SemIR::ClassId>();
}
if constexpr (RequiredIdKind == IdKind::InterfaceId) {
return back.id<SemIR::InterfaceId>();
}
if constexpr (RequiredIdKind == IdKind::NameId) {
return back.id<SemIR::NameId>();
}
Expand All @@ -239,6 +247,7 @@ class NodeStack {
InstBlockId,
FunctionId,
ClassId,
InterfaceId,
NameId,
TypeId,
// No associated ID type.
Expand All @@ -257,6 +266,8 @@ class NodeStack {
: parse_node(parse_node), function_id(function_id) {}
explicit Entry(Parse::NodeId parse_node, SemIR::ClassId class_id)
: parse_node(parse_node), class_id(class_id) {}
explicit Entry(Parse::NodeId parse_node, SemIR::InterfaceId interface_id)
: parse_node(parse_node), interface_id(interface_id) {}
explicit Entry(Parse::NodeId parse_node, SemIR::NameId name_id)
: parse_node(parse_node), name_id(name_id) {}
explicit Entry(Parse::NodeId parse_node, SemIR::TypeId type_id)
Expand All @@ -277,6 +288,9 @@ class NodeStack {
if constexpr (std::is_same<T, SemIR::ClassId>()) {
return class_id;
}
if constexpr (std::is_same<T, SemIR::InterfaceId>()) {
return interface_id;
}
if constexpr (std::is_same<T, SemIR::NameId>()) {
return name_id;
}
Expand All @@ -298,6 +312,7 @@ class NodeStack {
SemIR::InstBlockId inst_block_id;
SemIR::FunctionId function_id;
SemIR::ClassId class_id;
SemIR::InterfaceId interface_id;
SemIR::NameId name_id;
SemIR::TypeId type_id;
};
Expand Down Expand Up @@ -342,6 +357,8 @@ class NodeStack {
return IdKind::FunctionId;
case Parse::NodeKind::ClassDefinitionStart:
return IdKind::ClassId;
case Parse::NodeKind::InterfaceDefinitionStart:
return IdKind::InterfaceId;
case Parse::NodeKind::BaseName:
case Parse::NodeKind::IdentifierName:
return IdKind::NameId;
Expand All @@ -351,6 +368,7 @@ class NodeStack {
case Parse::NodeKind::FunctionIntroducer:
case Parse::NodeKind::IfStatementElse:
case Parse::NodeKind::ImplicitParamListStart:
case Parse::NodeKind::InterfaceIntroducer:
case Parse::NodeKind::LetIntroducer:
case Parse::NodeKind::ParamListStart:
case Parse::NodeKind::ParenExprOrTupleLiteralStart:
Expand Down Expand Up @@ -397,6 +415,9 @@ class NodeStack {
if constexpr (std::is_same_v<IdT, SemIR::ClassId>) {
return IdKind::ClassId;
}
if constexpr (std::is_same_v<IdT, SemIR::InterfaceId>) {
return IdKind::InterfaceId;
}
if constexpr (std::is_same_v<IdT, SemIR::NameId>) {
return IdKind::NameId;
}
Expand Down
38 changes: 38 additions & 0 deletions toolchain/check/testdata/interface/basic.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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

interface Empty {
}

interface ForwardDeclared;

interface ForwardDeclared {
fn F();
}

// CHECK:STDOUT: --- basic.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace {.Empty = %Empty.decl, .ForwardDeclared = %ForwardDeclared.decl.loc10}
// CHECK:STDOUT: %Empty.decl = interface_decl @Empty, ()
// CHECK:STDOUT: %ForwardDeclared.decl.loc10 = interface_decl @ForwardDeclared, ()
// CHECK:STDOUT: %ForwardDeclared.decl.loc12 = interface_decl @ForwardDeclared, ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @Empty {
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @ForwardDeclared {
// CHECK:STDOUT: %F: <function> = fn_decl @F
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .F = %F
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F();
// CHECK:STDOUT:
35 changes: 35 additions & 0 deletions toolchain/check/testdata/interface/fail_duplicate.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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

interface Interface { }

// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+6]]:21: ERROR: Redefinition of interface Interface.
// CHECK:STDERR: interface Interface {
// CHECK:STDERR: ^
// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-5]]:1: Previous definition was here.
// CHECK:STDERR: interface Interface { }
// CHECK:STDERR: ^~~~~~~~~
interface Interface {
fn F();
}

// CHECK:STDOUT: --- fail_duplicate.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace {.Interface = %Interface.decl.loc7}
// CHECK:STDOUT: %Interface.decl.loc7 = interface_decl @Interface, ()
// CHECK:STDOUT: %Interface.decl.loc15 = interface_decl @Interface, ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @Interface {
// CHECK:STDOUT: %F: <function> = fn_decl @F
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .F = %F
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F();
// CHECK:STDOUT:
Loading

0 comments on commit 3b0923c

Please sign in to comment.