mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-11-14 05:28:30 +00:00
The return type is deduced from the function's `return` statements (currently required to all have the same type), or from its first `returned var`. Calls to generic functions with deduced return types are not yet supported. When a function has a deduced return type, we defer the return type portion of the callee pattern match until deduction, and during deduction we extend the decl block and the pattern block with the return-related instructions. Conversions for `return expr;` statements are deferred until the return type has been deduced. The design requires that we take the common type of the returned types as the deduced return type, so we can't in general perform any conversions for returned expressions until we reach the end of the function. This is accomplished by inserting a `splice_block` where the conversions will go, and later updating that instruction to contain the conversion. Assisted-by: Claude Opus 5.5 via Antigravity
788 lines
32 KiB
C++
788 lines
32 KiB
C++
// 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
|
|
|
|
#include <optional>
|
|
#include <utility>
|
|
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/control_flow.h"
|
|
#include "toolchain/check/convert.h"
|
|
#include "toolchain/check/decl_introducer_state.h"
|
|
#include "toolchain/check/generic.h"
|
|
#include "toolchain/check/handle.h"
|
|
#include "toolchain/check/import_ref.h"
|
|
#include "toolchain/check/interface.h"
|
|
#include "toolchain/check/literal.h"
|
|
#include "toolchain/check/merge.h"
|
|
#include "toolchain/check/modifiers.h"
|
|
#include "toolchain/check/name_component.h"
|
|
#include "toolchain/check/name_lookup.h"
|
|
#include "toolchain/check/return.h"
|
|
#include "toolchain/check/type.h"
|
|
#include "toolchain/check/type_completion.h"
|
|
#include "toolchain/check/unused.h"
|
|
#include "toolchain/diagnostics/format_providers.h"
|
|
#include "toolchain/lex/token_kind.h"
|
|
#include "toolchain/parse/node_ids.h"
|
|
#include "toolchain/sem_ir/builtin_function_kind.h"
|
|
#include "toolchain/sem_ir/entry_point.h"
|
|
#include "toolchain/sem_ir/function.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/sem_ir/inst.h"
|
|
#include "toolchain/sem_ir/pattern.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
auto HandleParseNode(Context& context, Parse::FunctionIntroducerId node_id)
|
|
-> bool {
|
|
// The function is potentially generic.
|
|
StartGenericDecl(context);
|
|
// Create an instruction block to hold the instructions created as part of the
|
|
// function signature, such as parameter and return types.
|
|
context.inst_block_stack().Push();
|
|
// Push the bracketing node.
|
|
context.node_stack().Push(node_id);
|
|
// Optional modifiers and the name follow.
|
|
context.decl_introducer_state_stack().Push<Lex::TokenKind::Fn>();
|
|
context.decl_name_stack().PushScopeAndStartName();
|
|
return true;
|
|
}
|
|
|
|
// Handles a `->` or `->?` return specifier.
|
|
static auto HandleReturnSpecifier(Context& context,
|
|
Parse::AnyReturnSpecifierId node_id) -> bool {
|
|
auto [expr_node_id, expr_inst_id] = context.node_stack().PopExprWithNodeId();
|
|
// `-> T` provides a type expression, whereas `-> val T`, `-> ref T`,
|
|
// `-> var T`, and `->? F` provide a form.
|
|
auto expr_kind = context.parse_tree().node_kind(expr_node_id);
|
|
bool is_primitive_form = expr_kind == Parse::ValPrimitiveFormId::Kind ||
|
|
expr_kind == Parse::RefPrimitiveFormId::Kind ||
|
|
expr_kind == Parse::VarPrimitiveFormId::Kind;
|
|
bool is_return_type =
|
|
context.parse_tree().node_kind(node_id) == Parse::ReturnTypeId::Kind &&
|
|
!is_primitive_form;
|
|
if (is_return_type && context.parse_tree().node_kind(expr_node_id) ==
|
|
Parse::NodeKind::AutoTypeLiteral) {
|
|
// `-> auto`: the return type is deduced from the function body, so there is
|
|
// no return form or pattern yet. Push a marker in place of the pattern; see
|
|
// `PopReturnSpecifier`.
|
|
context.node_stack().Push(node_id, SemIR::AutoType::TypeInstId);
|
|
return true;
|
|
}
|
|
Context::FormExpr form_expr =
|
|
is_return_type ? ReturnExprAsForm(context, expr_node_id, expr_inst_id)
|
|
: FormExprAsForm(context, expr_node_id, expr_inst_id);
|
|
context.PushReturnForm(form_expr);
|
|
context.node_stack().Push(node_id,
|
|
AddReturnPattern(context, node_id, form_expr));
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::ReturnTypeId node_id) -> bool {
|
|
return HandleReturnSpecifier(context, node_id);
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::ReturnFormId node_id) -> bool {
|
|
return HandleReturnSpecifier(context, node_id);
|
|
}
|
|
|
|
// Diagnoses issues with the modifiers, removing modifiers that shouldn't be
|
|
// present.
|
|
static auto DiagnoseModifiers(Context& context,
|
|
Parse::AnyFunctionDeclId node_id,
|
|
DeclIntroducerState& introducer,
|
|
bool is_definition,
|
|
SemIR::NameScopeId parent_scope_id,
|
|
SemIR::InstId parent_scope_inst_id,
|
|
std::optional<SemIR::Inst> parent_scope_inst,
|
|
SemIR::InstId self_param_id) -> void {
|
|
CheckAccessModifiersOnDecl(context, introducer, parent_scope_inst);
|
|
LimitModifiersOnDecl(
|
|
context, introducer,
|
|
KeywordModifierSet::Access | KeywordModifierSet::Extern |
|
|
KeywordModifierSet::Export | KeywordModifierSet::Method |
|
|
KeywordModifierSet::Interface | KeywordModifierSet::Evaluation);
|
|
RestrictExternModifierOnDecl(context, introducer, parent_scope_inst,
|
|
is_definition);
|
|
CheckMethodModifiersOnFunction(context, introducer, parent_scope_inst_id,
|
|
parent_scope_inst);
|
|
RequireDefaultFinalOnlyInInterfaces(context, introducer, parent_scope_id,
|
|
is_definition);
|
|
// TODO: add check that functions in interfaces may only be defined if they
|
|
// are marked `default` or `final`.
|
|
|
|
if (!self_param_id.has_value() &&
|
|
introducer.modifier_set.HasAnyOf(KeywordModifierSet::Method)) {
|
|
CARBON_DIAGNOSTIC(VirtualWithoutSelf, Error, "virtual class function");
|
|
context.emitter().Emit(node_id, VirtualWithoutSelf);
|
|
introducer.modifier_set.Remove(KeywordModifierSet::Method);
|
|
}
|
|
}
|
|
|
|
// Returns the virtual-family modifier as an enum.
|
|
static auto GetVirtualModifier(const KeywordModifierSet& modifier_set)
|
|
-> SemIR::Function::VirtualModifier {
|
|
return modifier_set.ToEnum<SemIR::Function::VirtualModifier>()
|
|
.Case(KeywordModifierSet::Virtual,
|
|
SemIR::Function::VirtualModifier::Virtual)
|
|
.Case(KeywordModifierSet::Abstract,
|
|
SemIR::Function::VirtualModifier::Abstract)
|
|
.Case(KeywordModifierSet::Override,
|
|
SemIR::Function::VirtualModifier::Override)
|
|
.Default(SemIR::Function::VirtualModifier::None);
|
|
}
|
|
|
|
// Returns the evaluation modifier as an enum.
|
|
static auto GetEvaluationMode(const KeywordModifierSet& modifier_set)
|
|
-> SemIR::Function::EvaluationMode {
|
|
return modifier_set.ToEnum<SemIR::Function::EvaluationMode>()
|
|
.Case(KeywordModifierSet::Eval, SemIR::Function::EvaluationMode::Eval)
|
|
.Case(KeywordModifierSet::MustEval,
|
|
SemIR::Function::EvaluationMode::MustEval)
|
|
.Default(SemIR::Function::EvaluationMode::None);
|
|
}
|
|
|
|
// Returns the implementation modifier as an enum.
|
|
static auto GetInterfaceModifier(const KeywordModifierSet& modifier_set)
|
|
-> SemIR::Function::InterfaceModifier {
|
|
using enum SemIR::Function::InterfaceModifier;
|
|
return modifier_set.ToEnum<SemIR::Function::InterfaceModifier>()
|
|
.Case(KeywordModifierSet::Default, Default)
|
|
.Case(KeywordModifierSet::Final, Final)
|
|
.Default(None);
|
|
}
|
|
|
|
// Adds the declaration to name lookup when appropriate.
|
|
static auto MaybeAddToNameLookup(Context& context,
|
|
const DeclNameStack::NameContext& name_context,
|
|
const KeywordModifierSet& modifier_set,
|
|
SemIR::NameScopeId parent_scope_id,
|
|
SemIR::InstId decl_id) -> void {
|
|
if (name_context.state != DeclNameStack::NameContext::State::Poisoned &&
|
|
name_context.prev_inst_id().has_value()) {
|
|
return;
|
|
}
|
|
|
|
// At interface scope, a function declaration introduces an associated
|
|
// function.
|
|
auto lookup_result_id = decl_id;
|
|
if (parent_scope_id.has_value() && !name_context.has_qualifiers) {
|
|
if (auto interface_decl =
|
|
context.name_scopes().TryGetInstAs<SemIR::InterfaceWithSelfDecl>(
|
|
parent_scope_id)) {
|
|
lookup_result_id =
|
|
BuildAssociatedEntity(context, interface_decl->interface_id, decl_id);
|
|
}
|
|
}
|
|
|
|
context.decl_name_stack().AddName(name_context, lookup_result_id,
|
|
modifier_set.GetAccessKind());
|
|
}
|
|
|
|
// Returns whether the given type is `i32`.
|
|
static auto IsI32(Context& context, Parse::NodeId node_id,
|
|
SemIR::TypeId type_id) -> bool {
|
|
return type_id == MakeIntType(context, node_id, SemIR::IntKind::Signed,
|
|
context.ints().Add(32));
|
|
}
|
|
|
|
// Returns whether the given parameter list is valid for the entry point
|
|
// function `Main.Run`.
|
|
static auto IsValidEntryPointParamList(Context& context, Parse::NodeId node_id,
|
|
SemIR::InstBlockId param_patterns_id)
|
|
-> bool {
|
|
if (!param_patterns_id.has_value()) {
|
|
// Positional parameters for are not supported.
|
|
return false;
|
|
}
|
|
|
|
for (auto [index, param_pattern_id] :
|
|
llvm::enumerate(context.inst_blocks().Get(param_patterns_id))) {
|
|
if (param_pattern_id == SemIR::ErrorInst::InstId) {
|
|
// Ignore erroneous parameters.
|
|
continue;
|
|
}
|
|
|
|
// Validate that this is a by-value parameter, which is represented as an
|
|
// WrapperBindingPattern wrapping a ValueParamPattern.
|
|
auto type_id = SemIR::TypeId::None;
|
|
if (auto binding = context.insts().TryGetAs<SemIR::WrapperBindingPattern>(
|
|
param_pattern_id)) {
|
|
if (auto param_pattern =
|
|
context.insts().TryGetAs<SemIR::ValueParamPattern>(
|
|
binding->subpattern_id)) {
|
|
type_id = param_pattern->type_id;
|
|
}
|
|
}
|
|
if (!type_id.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
if (type_id == SemIR::ErrorInst::TypeId) {
|
|
// Ignore parameters with erroneous types.
|
|
continue;
|
|
}
|
|
|
|
auto param_type_inst_id = context.types()
|
|
.GetAs<SemIR::PatternType>(type_id)
|
|
.scrutinee_type_inst_id;
|
|
switch (index) {
|
|
case 0: {
|
|
// `argc` should be a 32-bit integer.
|
|
if (!IsI32(
|
|
context, node_id,
|
|
context.types().GetTypeIdForTypeInstId(param_type_inst_id))) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case 1: {
|
|
// `argv` should be a pointer.
|
|
// TODO: Consider checking the pointee type also.
|
|
if (!context.insts().Is<SemIR::PointerType>(param_type_inst_id)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
// TODO: Decide whether to allow a third `envp` parameter.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns whether the given return type is valid for the entry point
|
|
// function `Main.Run`.
|
|
static auto IsValidEntryPointReturnType(Context& context, Parse::NodeId node_id,
|
|
SemIR::TypeId return_type_id) -> bool {
|
|
// An implicit or explicit return type of `()` is OK.
|
|
// TODO: Translate this to returning an `i32` with value `0` in lowering.
|
|
if (!return_type_id.has_value()) {
|
|
return true;
|
|
}
|
|
if (return_type_id == GetTupleType(context, {})) {
|
|
return true;
|
|
}
|
|
|
|
if (IsI32(context, node_id, return_type_id)) {
|
|
// Explicit return type of `i32` or an adapter for it is OK.
|
|
return true;
|
|
}
|
|
|
|
// For now, disallow anything else.
|
|
// TODO: Decide on valid return types for `Main.Run`. Perhaps we should
|
|
// have an interface for this.
|
|
return false;
|
|
}
|
|
|
|
// If the function is the entry point, do corresponding validation.
|
|
static auto ValidateForEntryPoint(Context& context,
|
|
Parse::AnyFunctionDeclId node_id,
|
|
SemIR::FunctionId function_id,
|
|
const SemIR::Function& function_info)
|
|
-> void {
|
|
if (!SemIR::IsEntryPoint(context.sem_ir(), function_id)) {
|
|
return;
|
|
}
|
|
|
|
// TODO: Update this once valid signatures for the entry point are decided.
|
|
// See https://github.com/carbon-language/carbon-lang/issues/6735
|
|
if (function_info.implicit_param_patterns_id.has_value() ||
|
|
!IsValidEntryPointParamList(context, node_id,
|
|
function_info.param_patterns_id)) {
|
|
CARBON_DIAGNOSTIC(InvalidMainRunParameters, Error,
|
|
"invalid parameters for `Main.Run` function; expected "
|
|
"`()` or `(argc: i32, argv: Core.Optional(char*)*)`");
|
|
context.emitter().Emit(node_id, InvalidMainRunParameters);
|
|
} else if (!IsValidEntryPointReturnType(
|
|
context, node_id,
|
|
function_info.GetDeclaredReturnType(context.sem_ir()))) {
|
|
CARBON_DIAGNOSTIC(InvalidMainRunReturnType, Error,
|
|
"invalid return type for `Main.Run` function; expected "
|
|
"`fn (...)` or `fn (...) -> i32`");
|
|
context.emitter().Emit(node_id, InvalidMainRunReturnType);
|
|
}
|
|
}
|
|
|
|
static auto IsGenericFunction(Context& context,
|
|
SemIR::GenericId function_generic_id,
|
|
SemIR::GenericId class_generic_id) -> bool {
|
|
if (function_generic_id == SemIR::GenericId::None) {
|
|
return false;
|
|
}
|
|
|
|
if (class_generic_id == SemIR::GenericId::None) {
|
|
return true;
|
|
}
|
|
|
|
const auto& function_generic = context.generics().Get(function_generic_id);
|
|
const auto& class_generic = context.generics().Get(class_generic_id);
|
|
|
|
auto function_bindings =
|
|
context.inst_blocks().Get(function_generic.bindings_id);
|
|
auto class_bindings = context.inst_blocks().Get(class_generic.bindings_id);
|
|
|
|
// If the function's bindings are the same size as the class's bindings,
|
|
// then there are no extra bindings for the function, so it is effectively
|
|
// non-generic within the scope of a specific of the class.
|
|
return class_bindings.size() != function_bindings.size();
|
|
}
|
|
|
|
// Requests a vtable be created when processing a virtual function.
|
|
static auto RequestVtableIfVirtual(
|
|
Context& context, Parse::AnyFunctionDeclId node_id,
|
|
SemIR::Function::VirtualModifier& virtual_modifier,
|
|
const std::optional<SemIR::Inst>& parent_scope_inst, SemIR::InstId decl_id,
|
|
SemIR::GenericId generic_id) -> void {
|
|
// In order to request a vtable, the function must be virtual, and in a class
|
|
// scope.
|
|
if (virtual_modifier == SemIR::Function::VirtualModifier::None ||
|
|
!parent_scope_inst) {
|
|
return;
|
|
}
|
|
auto class_decl = parent_scope_inst->TryAs<SemIR::ClassDecl>();
|
|
if (!class_decl) {
|
|
return;
|
|
}
|
|
|
|
auto& class_info = context.classes().Get(class_decl->class_id);
|
|
if (virtual_modifier == SemIR::Function::VirtualModifier::Override &&
|
|
!class_info.base_id.has_value()) {
|
|
CARBON_DIAGNOSTIC(OverrideWithoutBase, Error,
|
|
"override without base class");
|
|
context.emitter().Emit(node_id, OverrideWithoutBase);
|
|
virtual_modifier = SemIR::Function::VirtualModifier::None;
|
|
return;
|
|
}
|
|
|
|
if (IsGenericFunction(context, generic_id, class_info.generic_id)) {
|
|
CARBON_DIAGNOSTIC(GenericVirtual, Error, "generic virtual function");
|
|
context.emitter().Emit(node_id, GenericVirtual);
|
|
virtual_modifier = SemIR::Function::VirtualModifier::None;
|
|
return;
|
|
}
|
|
|
|
// TODO: If this is an `impl` function, check there's a matching base
|
|
// function that's impl or virtual.
|
|
class_info.is_dynamic = true;
|
|
context.vtable_stack().AddInstId(decl_id);
|
|
}
|
|
|
|
// Diagnoses if a deduced return type is not permitted for the given function
|
|
// declaration, and returns whether it is permitted. If not, clears any virtual
|
|
// modifier, because a virtual function needs a known return type.
|
|
static auto CheckDeducedReturnTypeAllowed(
|
|
Context& context, Parse::AnyFunctionDeclId node_id,
|
|
Parse::NodeId return_node_id, bool is_definition, bool is_extern,
|
|
SemIR::Function::VirtualModifier& virtual_modifier,
|
|
const std::optional<SemIR::Inst>& parent_scope_inst) -> bool {
|
|
enum class Reason : int8_t { Builtin, Extern, InterfaceMember, Virtual };
|
|
std::optional<Reason> reason;
|
|
if (context.parse_tree().node_kind(node_id) ==
|
|
Parse::NodeKind::BuiltinFunctionDefinitionStart) {
|
|
// A builtin function has no body to deduce from.
|
|
reason = Reason::Builtin;
|
|
} else if (is_extern) {
|
|
// An `extern` function is declared elsewhere.
|
|
reason = Reason::Extern;
|
|
} else if (parent_scope_inst &&
|
|
parent_scope_inst->Is<SemIR::InterfaceWithSelfDecl>()) {
|
|
// An associated function's return type is part of the interface's contract
|
|
// with its impls, so shouldn't depend on a `default` definition.
|
|
// TODO: A `final` function can't be overridden, so deducing its return
|
|
// type from its definition would be reasonable. Consider allowing that
|
|
// once calls to generic functions with deduced return types are supported.
|
|
reason = Reason::InterfaceMember;
|
|
} else if (virtual_modifier != SemIR::Function::VirtualModifier::None) {
|
|
// The vtable is built before the function body is checked.
|
|
reason = Reason::Virtual;
|
|
virtual_modifier = SemIR::Function::VirtualModifier::None;
|
|
}
|
|
if (reason) {
|
|
CARBON_DIAGNOSTIC(DeducedReturnTypeNotAllowed, Error,
|
|
"{0:=0:builtin function|=1:`extern` function|=2:"
|
|
"interface member|=3:virtual function} with deduced "
|
|
"return type",
|
|
Diagnostics::IntAsSelect);
|
|
context.emitter().Emit(return_node_id, DeducedReturnTypeNotAllowed,
|
|
static_cast<int>(*reason));
|
|
return false;
|
|
}
|
|
|
|
// The return type is deduced from the function body, so only a definition
|
|
// can declare one. As a consequence, the function can't be redeclared.
|
|
if (!is_definition) {
|
|
CARBON_DIAGNOSTIC(DeducedReturnTypeNotDefinition, Error,
|
|
"forward declaration of function with deduced return "
|
|
"type");
|
|
context.emitter().Emit(return_node_id, DeducedReturnTypeNotDefinition);
|
|
return false;
|
|
}
|
|
|
|
if (parent_scope_inst && parent_scope_inst->Is<SemIR::ImplDecl>()) {
|
|
// TODO: The impl witness and any thunks are built before the function body
|
|
// is checked.
|
|
context.TODO(return_node_id, "deduced return type for `impl` member");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Build a FunctionDecl describing the signature of a function. This
|
|
// handles the common logic shared by function declaration syntax and function
|
|
// definition syntax.
|
|
static auto BuildFunctionDecl(Context& context,
|
|
Parse::AnyFunctionDeclId node_id,
|
|
bool is_definition)
|
|
-> std::pair<SemIR::FunctionId, SemIR::InstId> {
|
|
auto return_specifier = PopReturnSpecifier(context);
|
|
|
|
auto name = PopNameComponent(context, return_specifier.pattern_id,
|
|
return_specifier.has_return_specifier());
|
|
auto name_context = context.decl_name_stack().FinishName(name);
|
|
|
|
context.node_stack()
|
|
.PopAndDiscardSoloNodeId<Parse::NodeKind::FunctionIntroducer>();
|
|
|
|
auto self_param_id = FindSelfPattern(context, name.implicit_param_patterns_id,
|
|
name.param_patterns_id);
|
|
|
|
// `self` must be the first explicit parameter. (Declaring it in the implicit
|
|
// parameter list or outside any parameter list is diagnosed earlier.)
|
|
if (self_param_id.has_value()) {
|
|
auto explicit_params =
|
|
context.inst_blocks().GetOrEmpty(name.param_patterns_id);
|
|
if (!explicit_params.empty() && explicit_params.front() != self_param_id &&
|
|
llvm::is_contained(explicit_params, self_param_id)) {
|
|
CARBON_DIAGNOSTIC(SelfNotFirstParam, Error,
|
|
"`self` must be the first explicit parameter");
|
|
context.emitter().Emit(SemIR::LocId(self_param_id), SelfNotFirstParam);
|
|
}
|
|
}
|
|
|
|
// Process modifiers.
|
|
auto [parent_scope_inst_id, parent_scope_inst] =
|
|
context.name_scopes().GetInstIfValid(name_context.parent_scope_id);
|
|
auto introducer =
|
|
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Fn>();
|
|
DiagnoseModifiers(context, node_id, introducer, is_definition,
|
|
name_context.parent_scope_id, parent_scope_inst_id,
|
|
parent_scope_inst, self_param_id);
|
|
bool is_extern = introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extern);
|
|
auto virtual_modifier = GetVirtualModifier(introducer.modifier_set);
|
|
auto evaluation_mode = GetEvaluationMode(introducer.modifier_set);
|
|
auto interface_modifier = GetInterfaceModifier(introducer.modifier_set);
|
|
|
|
// If the return type is deduced in a context where that's not allowed, we
|
|
// will treat the function as having an invalid return type.
|
|
bool is_invalid_deduced_return =
|
|
return_specifier.is_deduced &&
|
|
!CheckDeducedReturnTypeAllowed(context, node_id, return_specifier.node_id,
|
|
is_definition, is_extern, virtual_modifier,
|
|
parent_scope_inst);
|
|
|
|
// Add the function declaration.
|
|
SemIR::FunctionDecl function_decl = {SemIR::TypeId::None,
|
|
SemIR::FunctionId::None,
|
|
context.inst_block_stack().Pop()};
|
|
auto decl_id = AddPlaceholderInst(context, node_id, function_decl);
|
|
|
|
// Build the function entity. This will be merged into an existing function if
|
|
// there is one, or otherwise added to the function store.
|
|
auto function_info = SemIR::Function{
|
|
name_context.MakeEntityWithParamsBase(name, decl_id, is_extern,
|
|
introducer.extern_library),
|
|
{
|
|
.call_param_patterns_id = name.call_param_patterns_id,
|
|
.call_params_id = name.call_params_id,
|
|
.call_param_ranges = name.param_ranges,
|
|
.return_type_inst_id = return_specifier.form.type_component_inst_id,
|
|
.return_form_inst_id = return_specifier.form.form_inst_id,
|
|
.return_pattern_id = return_specifier.pattern_id,
|
|
.virtual_modifier = virtual_modifier,
|
|
.evaluation_mode = evaluation_mode,
|
|
.interface_modifier = interface_modifier,
|
|
.has_deduced_return_type = return_specifier.is_deduced,
|
|
.self_param_id = self_param_id,
|
|
}};
|
|
if (is_definition) {
|
|
function_info.definition_id = decl_id;
|
|
}
|
|
|
|
CheckFunctionParams(context, function_info);
|
|
|
|
TryMergeRedecl(
|
|
context, name_context, std::nullopt,
|
|
MergeRedeclEntityInfo<SemIR::Function>{.new_entity_decl = function_decl,
|
|
.new_entity = function_info},
|
|
is_definition);
|
|
|
|
// Create a new function if this isn't a valid redeclaration.
|
|
if (!function_decl.function_id.has_value()) {
|
|
if (function_info.is_extern && context.sem_ir().is_impl()) {
|
|
DiagnoseExternRequiresDeclInApiFile(context, node_id);
|
|
}
|
|
function_info.generic_id = BuildGenericDecl(context, decl_id);
|
|
function_decl.function_id = context.functions().Add(function_info);
|
|
function_decl.type_id =
|
|
GetFunctionType(context, function_decl.function_id,
|
|
context.scope_stack().PeekSpecificId());
|
|
} else {
|
|
auto prev_decl_generic_id =
|
|
context.functions().Get(function_decl.function_id).generic_id;
|
|
FinishGenericRedecl(context, prev_decl_generic_id);
|
|
// TODO: Validate that the redeclaration doesn't set an access modifier.
|
|
}
|
|
|
|
RequestVtableIfVirtual(context, node_id, function_info.virtual_modifier,
|
|
parent_scope_inst, decl_id, function_info.generic_id);
|
|
|
|
// Write the function ID into the FunctionDecl.
|
|
ReplaceInstBeforeConstantUse(context, decl_id, function_decl);
|
|
|
|
// If we diagnosed the deduced return type, recover by giving the function an
|
|
// invalid return type.
|
|
if (is_invalid_deduced_return) {
|
|
SetDeducedReturnType(context, function_decl.function_id,
|
|
return_specifier.node_id, SemIR::ErrorInst::TypeId);
|
|
}
|
|
|
|
// Diagnose 'definition of `abstract` function' using the canonical Function's
|
|
// modifiers.
|
|
if (is_definition &&
|
|
context.functions().Get(function_decl.function_id).virtual_modifier ==
|
|
SemIR::Function::VirtualModifier::Abstract) {
|
|
CARBON_DIAGNOSTIC(DefinedAbstractFunction, Error,
|
|
"definition of `abstract` function");
|
|
context.emitter().Emit(LocIdForDiagnostics::TokenOnly(node_id),
|
|
DefinedAbstractFunction);
|
|
}
|
|
|
|
// Add to name lookup if needed, now that the decl is built.
|
|
MaybeAddToNameLookup(context, name_context, introducer.modifier_set,
|
|
name_context.parent_scope_id, decl_id);
|
|
|
|
// If the return type is deduced, we validate the entry point at the end of
|
|
// the definition, once the return type is known.
|
|
if (!return_specifier.is_deduced) {
|
|
ValidateForEntryPoint(context, node_id, function_decl.function_id,
|
|
function_info);
|
|
}
|
|
|
|
if (!is_definition && context.sem_ir().is_impl() && !is_extern) {
|
|
context.definitions_required_by_decl().push_back(decl_id);
|
|
}
|
|
|
|
return {function_decl.function_id, decl_id};
|
|
}
|
|
|
|
// Checks that the `unused` modifier is only used when there is a definition,
|
|
// and emits a diagnostic for every binding that is marked `unused`.
|
|
static auto CheckUnusedBindingsInPattern(Context& context,
|
|
SemIR::InstId pattern_id) -> void {
|
|
llvm::SmallVector<SemIR::InstId> work_list;
|
|
work_list.push_back(pattern_id);
|
|
|
|
while (!work_list.empty()) {
|
|
auto current_id = work_list.pop_back_val();
|
|
auto inst = context.insts().Get(current_id);
|
|
CARBON_KIND_SWITCH(inst) {
|
|
case CARBON_KIND_ANY(SemIR::AnyLeafParamPattern, _): {
|
|
break;
|
|
}
|
|
case CARBON_KIND_ANY(SemIR::AnyBindingPattern, bind): {
|
|
auto& entity_name = context.entity_names().Get(bind.entity_name_id);
|
|
// We need special treatment for the name "_" which is implicitly
|
|
// unused but actually permitted without a definition.
|
|
if (entity_name.is_unused &&
|
|
entity_name.name_id != SemIR::NameId::Underscore) {
|
|
CARBON_DIAGNOSTIC(UnusedModifierWithoutDefinition, Error,
|
|
"`unused` modifier without a definition");
|
|
context.emitter().Emit(current_id, UnusedModifierWithoutDefinition);
|
|
}
|
|
if (bind.kind == SemIR::WrapperBindingPattern::Kind) {
|
|
work_list.push_back(bind.subpattern_id);
|
|
}
|
|
break;
|
|
}
|
|
case CARBON_KIND_ANY(SemIR::AnyVarPattern, var_pattern): {
|
|
work_list.push_back(var_pattern.subpattern_id);
|
|
break;
|
|
}
|
|
case CARBON_KIND(SemIR::TuplePattern tuple_pattern): {
|
|
auto elements = context.inst_blocks().Get(tuple_pattern.elements_id);
|
|
for (auto element_id : llvm::reverse(elements)) {
|
|
work_list.push_back(element_id);
|
|
}
|
|
break;
|
|
}
|
|
case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): {
|
|
work_list.push_back(default_value_pattern.subpattern_id);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static auto DiagnoseUnusedMarkersWithoutDefinition(
|
|
Context& context, SemIR::FunctionId function_id) -> void {
|
|
const auto& function = context.functions().Get(function_id);
|
|
// The `unused` modifier requires a definition, so it is not valid on any
|
|
// parameter when there is none. This applies to implicit parameters (such as
|
|
// `T: type` introduced by `[...]`) too, so check the implicit parameter list
|
|
// as well as the explicit one.
|
|
for (auto param_patterns_id :
|
|
{function.implicit_param_patterns_id, function.param_patterns_id}) {
|
|
if (param_patterns_id.has_value()) {
|
|
for (auto pattern_id : context.inst_blocks().Get(param_patterns_id)) {
|
|
CheckUnusedBindingsInPattern(context, pattern_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::FunctionDeclId node_id) -> bool {
|
|
auto [function_id, decl_id] =
|
|
BuildFunctionDecl(context, node_id, /*is_definition=*/false);
|
|
DiagnoseUnusedMarkersWithoutDefinition(context, function_id);
|
|
context.decl_name_stack().PopScope();
|
|
return true;
|
|
}
|
|
|
|
// Processes a function definition after a signature for which we have already
|
|
// built a function ID. This logic is shared between processing regular function
|
|
// definitions and delayed parsing of inline method definitions.
|
|
static auto HandleFunctionDefinitionAfterSignature(
|
|
Context& context, Parse::FunctionDefinitionStartId node_id,
|
|
SemIR::FunctionId function_id, SemIR::InstId decl_id) -> void {
|
|
StartFunctionDefinition(context, decl_id, function_id);
|
|
context.node_stack().Push(node_id, function_id);
|
|
}
|
|
|
|
auto HandleFunctionDefinitionSuspend(Context& context,
|
|
Parse::FunctionDefinitionStartId node_id)
|
|
-> DeferredDefinitionWorklist::SuspendedFunction {
|
|
// Process the declaration portion of the function.
|
|
auto [function_id, decl_id] =
|
|
BuildFunctionDecl(context, node_id, /*is_definition=*/true);
|
|
return {.function_id = function_id,
|
|
.decl_id = decl_id,
|
|
.saved_name_state = context.decl_name_stack().Suspend()};
|
|
}
|
|
|
|
auto HandleFunctionDefinitionResume(
|
|
Context& context, Parse::FunctionDefinitionStartId node_id,
|
|
DeferredDefinitionWorklist::SuspendedFunction&& suspended_fn) -> void {
|
|
context.decl_name_stack().Restore(std::move(suspended_fn.saved_name_state));
|
|
HandleFunctionDefinitionAfterSignature(
|
|
context, node_id, suspended_fn.function_id, suspended_fn.decl_id);
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::FunctionDefinitionStartId node_id)
|
|
-> bool {
|
|
// Process the declaration portion of the function.
|
|
auto [function_id, decl_id] =
|
|
BuildFunctionDecl(context, node_id, /*is_definition=*/true);
|
|
HandleFunctionDefinitionAfterSignature(context, node_id, function_id,
|
|
decl_id);
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::FunctionDefinitionId node_id)
|
|
-> bool {
|
|
auto [start_node_id, function_id] =
|
|
context.node_stack()
|
|
.PopWithNodeId<Parse::NodeKind::FunctionDefinitionStart>();
|
|
|
|
CheckFunctionReturnOnFinish(context, node_id, function_id);
|
|
FinishFunctionDefinition(context, function_id);
|
|
|
|
// Now that the return type has been deduced, we can validate the entry point.
|
|
const auto& function = context.functions().Get(function_id);
|
|
if (function.has_deduced_return_type) {
|
|
ValidateForEntryPoint(context, start_node_id, function_id, function);
|
|
}
|
|
|
|
context.decl_name_stack().PopScope(/*check_unused=*/true);
|
|
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context,
|
|
Parse::BuiltinFunctionDefinitionStartId node_id) -> bool {
|
|
// Process the declaration portion of the function.
|
|
auto [function_id, _] =
|
|
BuildFunctionDecl(context, node_id, /*is_definition=*/true);
|
|
context.node_stack().Push(node_id, function_id);
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::BuiltinNameId node_id) -> bool {
|
|
context.node_stack().Push(node_id);
|
|
return true;
|
|
}
|
|
|
|
// Looks up a builtin function kind given its name as a string.
|
|
// TODO: Move this out to another file.
|
|
static auto LookupBuiltinFunctionKind(Context& context,
|
|
Parse::BuiltinNameId name_id)
|
|
-> SemIR::BuiltinFunctionKind {
|
|
auto builtin_name = context.string_literal_values().Get(
|
|
context.tokens().GetStringLiteralValue(
|
|
context.parse_tree().node_token(name_id)));
|
|
auto kind = SemIR::BuiltinFunctionKind::ForBuiltinName(builtin_name);
|
|
if (kind == SemIR::BuiltinFunctionKind::None) {
|
|
CARBON_DIAGNOSTIC(UnknownBuiltinFunctionName, Error,
|
|
"unknown builtin function name \"{0}\"", std::string);
|
|
context.emitter().Emit(name_id, UnknownBuiltinFunctionName,
|
|
builtin_name.str());
|
|
}
|
|
return kind;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context,
|
|
Parse::BuiltinFunctionDefinitionId /*node_id*/) -> bool {
|
|
auto name_id =
|
|
context.node_stack().PopForSoloNodeId<Parse::NodeKind::BuiltinName>();
|
|
auto [fn_node_id, function_id] =
|
|
context.node_stack()
|
|
.PopWithNodeId<Parse::NodeKind::BuiltinFunctionDefinitionStart>();
|
|
|
|
auto builtin_kind = LookupBuiltinFunctionKind(context, name_id);
|
|
if (builtin_kind != SemIR::BuiltinFunctionKind::None) {
|
|
CheckFunctionDefinitionSignature(context, function_id);
|
|
|
|
auto& function = context.functions().Get(function_id);
|
|
if (IsValidBuiltinDeclaration(context, function, builtin_kind)) {
|
|
function.SetBuiltinFunction(builtin_kind);
|
|
// Build an empty generic definition if this is a generic builtin.
|
|
StartGenericDefinition(context, function.generic_id);
|
|
FinishGenericDefinition(context, function.generic_id);
|
|
} else {
|
|
CARBON_DIAGNOSTIC(InvalidBuiltinSignature, Error,
|
|
"invalid signature for builtin function \"{0}\"",
|
|
std::string);
|
|
context.emitter().Emit(fn_node_id, InvalidBuiltinSignature,
|
|
builtin_kind.name().str());
|
|
}
|
|
}
|
|
context.decl_name_stack().PopScope();
|
|
return true;
|
|
}
|
|
|
|
auto HandleParseNode(Context& context, Parse::FunctionTerseDefinitionId node_id)
|
|
-> bool {
|
|
return context.TODO(node_id, "HandleFunctionTerseDefinition");
|
|
}
|
|
|
|
} // namespace Carbon::Check
|