From 1bf9b243032e4599ca21f9b24bdea7486405e557 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Wed, 6 Mar 2024 12:17:29 -0800 Subject: [PATCH] Expand handling of function redeclarations. (#3739) This detects more cases of incorrect matches between declaration and definition. It also factors the logic out to a separate file for easier sharing, particularly when it comes to merging imports (I'm not sure if this exact API will be reshared, but the core logic should apply). --- toolchain/check/BUILD | 12 + toolchain/check/function.cpp | 221 ++++++++++++++++++ toolchain/check/function.h | 26 +++ toolchain/check/handle_function.cpp | 73 +++--- .../testdata/class/fail_redefinition.carbon | 6 + .../check/testdata/class/fail_self.carbon | 8 +- .../function/declaration/fail_redecl.carbon | 103 ++++++++ .../fail_decl_param_mismatch.carbon | 154 ++++++++++++ .../function/definition/fail_redef.carbon | 33 +++ .../testdata/namespace/fail_duplicate.carbon | 2 +- toolchain/diagnostics/diagnostic_kind.def | 20 +- 11 files changed, 604 insertions(+), 54 deletions(-) create mode 100644 toolchain/check/function.cpp create mode 100644 toolchain/check/function.h create mode 100644 toolchain/check/testdata/function/declaration/fail_redecl.carbon create mode 100644 toolchain/check/testdata/function/definition/fail_decl_param_mismatch.carbon create mode 100644 toolchain/check/testdata/function/definition/fail_redef.carbon diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 161b1ec462b1..8eea88a7c59a 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -101,6 +101,7 @@ cc_library( hdrs = ["check.h"], deps = [ ":context", + ":function", ":import", ":interface", "//common:check", @@ -133,6 +134,17 @@ cc_fuzz_test( ], ) +cc_library( + name = "function", + srcs = ["function.cpp"], + hdrs = ["function.h"], + deps = [ + ":context", + "//common:check", + "//toolchain/sem_ir:file", + ], +) + cc_library( name = "import", srcs = ["import.cpp"], diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp new file mode 100644 index 000000000000..77169114dd04 --- /dev/null +++ b/toolchain/check/function.cpp @@ -0,0 +1,221 @@ +// 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 "toolchain/check/function.h" + +namespace Carbon::Check { + +// Returns true if there was an error in declaring the function, which will have +// previously been diagnosed. +static auto FunctionDeclHasError(Context& context, const SemIR::Function& fn) + -> bool { + if (fn.return_type_id == SemIR::TypeId::Error) { + return true; + } + for (auto param_refs_id : {fn.implicit_param_refs_id, fn.param_refs_id}) { + if (param_refs_id != SemIR::InstBlockId::Empty) { + for (auto param_id : context.inst_blocks().Get(param_refs_id)) { + if (context.insts().Get(param_id).type_id() == SemIR::TypeId::Error) { + return true; + } + } + } + } + return false; +} + +// Returns false if a param differs for a redeclaration. The caller is expected +// to provide a diagnostic. +static auto CheckRedeclParam(Context& context, + llvm::StringLiteral param_diag_label, + int32_t param_index, + SemIR::InstId new_param_ref_id, + SemIR::InstId prev_param_ref_id) -> bool { + // TODO: Consider differentiating between type and name mistakes. For now, + // taking the simpler approach because I also think we may want to refactor + // params. + auto diagnose = [&]() { + CARBON_DIAGNOSTIC(FunctionRedeclParamDiffers, Error, + "Function redeclaration differs at {0}parameter {1}.", + llvm::StringLiteral, int32_t); + CARBON_DIAGNOSTIC(FunctionRedeclParamPrevious, Note, + "Previous declaration's corresponding {0}parameter here.", + llvm::StringLiteral); + context.emitter() + .Build(new_param_ref_id, FunctionRedeclParamDiffers, param_diag_label, + param_index + 1) + .Note(prev_param_ref_id, FunctionRedeclParamPrevious, param_diag_label) + .Emit(); + }; + + auto new_param_ref = context.insts().Get(new_param_ref_id); + auto prev_param_ref = context.insts().Get(prev_param_ref_id); + if (new_param_ref.kind() != prev_param_ref.kind() || + new_param_ref.type_id() != prev_param_ref.type_id()) { + diagnose(); + return false; + } + + if (new_param_ref.Is()) { + new_param_ref = + context.insts().Get(new_param_ref.As().inner_id); + prev_param_ref = + context.insts().Get(prev_param_ref.As().inner_id); + if (new_param_ref.kind() != prev_param_ref.kind()) { + diagnose(); + return false; + } + } + + if (new_param_ref.Is()) { + new_param_ref = + context.insts().Get(new_param_ref.As().value_id); + prev_param_ref = + context.insts().Get(prev_param_ref.As().value_id); + } + + auto new_param = new_param_ref.As(); + auto prev_param = prev_param_ref.As(); + if (new_param.name_id != prev_param.name_id) { + diagnose(); + return false; + } + + return true; +} + +// Returns false if the param refs differ for a redeclaration. +static auto CheckRedeclParams(Context& context, SemIR::InstId new_decl_id, + SemIR::InstBlockId new_param_refs_id, + SemIR::InstId prev_decl_id, + SemIR::InstBlockId prev_param_refs_id, + llvm::StringLiteral param_diag_label) -> bool { + // This will often occur for empty params. + if (new_param_refs_id == prev_param_refs_id) { + return true; + } + const auto new_param_ref_ids = context.inst_blocks().Get(new_param_refs_id); + const auto prev_param_ref_ids = context.inst_blocks().Get(prev_param_refs_id); + if (new_param_ref_ids.size() != prev_param_ref_ids.size()) { + CARBON_DIAGNOSTIC( + FunctionRedeclParamCountDiffers, Error, + "Function redeclaration differs because of {0}parameter count of {1}.", + llvm::StringLiteral, int32_t); + CARBON_DIAGNOSTIC(FunctionRedeclParamCountPrevious, Note, + "Previously declared with {0}parameter count of {1}.", + llvm::StringLiteral, int32_t); + context.emitter() + .Build(new_decl_id, FunctionRedeclParamCountDiffers, param_diag_label, + new_param_ref_ids.size()) + .Note(prev_decl_id, FunctionRedeclParamCountPrevious, param_diag_label, + prev_param_ref_ids.size()) + .Emit(); + return false; + } + for (auto [index, new_param_ref_id, prev_param_ref_id] : + llvm::enumerate(new_param_ref_ids, prev_param_ref_ids)) { + if (!CheckRedeclParam(context, param_diag_label, index, new_param_ref_id, + prev_param_ref_id)) { + return false; + } + } + return true; +} + +// Returns false if the provided function declarations differ. +static auto CheckRedecl(Context& context, const SemIR::Function& new_function, + const SemIR::Function& prev_function) -> bool { + if (FunctionDeclHasError(context, new_function) || + FunctionDeclHasError(context, prev_function)) { + return false; + } + if (!CheckRedeclParams(context, new_function.decl_id, + new_function.implicit_param_refs_id, + prev_function.decl_id, + prev_function.implicit_param_refs_id, "implicit ") || + !CheckRedeclParams(context, new_function.decl_id, + new_function.param_refs_id, prev_function.decl_id, + prev_function.param_refs_id, "")) { + return false; + } + if (new_function.return_type_id != prev_function.return_type_id) { + CARBON_DIAGNOSTIC( + FunctionRedeclReturnTypeDiffers, Error, + "Function redeclaration differs because return type is `{0}`.", + SemIR::TypeId); + CARBON_DIAGNOSTIC( + FunctionRedeclReturnTypeDiffersNoReturn, Error, + "Function redeclaration differs because no return type is provided."); + auto diag = + new_function.return_type_id.is_valid() + ? context.emitter().Build(new_function.decl_id, + FunctionRedeclReturnTypeDiffers, + new_function.return_type_id) + : context.emitter().Build(new_function.decl_id, + FunctionRedeclReturnTypeDiffersNoReturn); + if (prev_function.return_type_id.is_valid()) { + CARBON_DIAGNOSTIC(FunctionRedeclReturnTypePrevious, Note, + "Previously declared with return type `{0}`.", + SemIR::TypeId); + diag.Note(prev_function.decl_id, FunctionRedeclReturnTypePrevious, + prev_function.return_type_id); + } else { + CARBON_DIAGNOSTIC(FunctionRedeclReturnTypePreviousNoReturn, Note, + "Previously declared with no return type."); + diag.Note(prev_function.decl_id, + FunctionRedeclReturnTypePreviousNoReturn); + } + diag.Emit(); + return false; + } + + return true; +} + +auto MergeFunctionRedecl(Context& context, Parse::NodeId parse_node, + SemIR::Function& new_function, + SemIR::FunctionId prev_function_id, bool is_definition) + -> bool { + auto& prev_function = context.functions().Get(prev_function_id); + + // TODO: Disallow redeclarations within classes? + if (!CheckRedecl(context, new_function, prev_function)) { + return false; + } + + if (!is_definition) { + CARBON_DIAGNOSTIC(FunctionRedecl, Error, + "Redundant redeclaration of function {0}.", + SemIR::NameId); + CARBON_DIAGNOSTIC(FunctionPreviousDecl, Note, "Previously declared here."); + context.emitter() + .Build(parse_node, FunctionRedecl, prev_function.name_id) + .Note(prev_function.decl_id, FunctionPreviousDecl) + .Emit(); + // The diagnostic doesn't prevent a merge. + return true; + } else if (prev_function.definition_id.is_valid()) { + CARBON_DIAGNOSTIC(FunctionRedefinition, Error, + "Redefinition of function {0}.", SemIR::NameId); + CARBON_DIAGNOSTIC(FunctionPreviousDefinition, Note, + "Previously defined here."); + context.emitter() + .Build(parse_node, FunctionRedefinition, prev_function.name_id) + .Note(prev_function.definition_id, FunctionPreviousDefinition) + .Emit(); + // The second definition will be unused as a consequence of the error. + return true; + } + + // Track the signature from the definition, so that IDs in the body + // match IDs in the signature. + prev_function.definition_id = new_function.definition_id; + prev_function.implicit_param_refs_id = new_function.implicit_param_refs_id; + prev_function.param_refs_id = new_function.param_refs_id; + prev_function.return_type_id = new_function.return_type_id; + prev_function.return_slot_id = new_function.return_slot_id; + return true; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/function.h b/toolchain/check/function.h new file mode 100644 index 000000000000..9bd4ec02b522 --- /dev/null +++ b/toolchain/check/function.h @@ -0,0 +1,26 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_FUNCTION_H_ +#define CARBON_TOOLCHAIN_CHECK_FUNCTION_H_ + +#include "toolchain/check/context.h" +#include "toolchain/sem_ir/function.h" + +namespace Carbon::Check { + +// Tries to merge new_function into prev_function_id. Since new_function won't +// have a definition even if one is upcoming, set is_definition to indicate the +// planned result. +// +// If merging is successful, updates the FunctionId on new_function and returns +// true. Otherwise, returns false. Prints a diagnostic when appropriate. +auto MergeFunctionRedecl(Context& context, Parse::NodeId parse_node, + SemIR::Function& new_function, + SemIR::FunctionId prev_function_id, bool is_definition) + -> bool; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_FUNCTION_H_ diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index caaab6719577..788517367643 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -5,11 +5,14 @@ #include "toolchain/check/context.h" #include "toolchain/check/convert.h" #include "toolchain/check/decl_name_stack.h" +#include "toolchain/check/function.h" #include "toolchain/check/interface.h" #include "toolchain/check/modifiers.h" #include "toolchain/parse/tree_node_location_translator.h" #include "toolchain/sem_ir/entry_point.h" +#include "toolchain/sem_ir/function.h" #include "toolchain/sem_ir/ids.h" +#include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -117,12 +120,21 @@ static auto BuildFunctionDecl(Context& context, auto function_decl = SemIR::FunctionDecl{ context.GetBuiltinType(SemIR::BuiltinKind::FunctionType), SemIR::FunctionId::Invalid, decl_block_id}; - auto function_decl_id = - context.AddPlaceholderInst({parse_node, function_decl}); + auto function_info = SemIR::Function{ + .name_id = name_context.name_id_for_new_inst(), + .enclosing_scope_id = name_context.enclosing_scope_id_for_new_inst(), + .decl_id = context.AddPlaceholderInst({parse_node, function_decl}), + .implicit_param_refs_id = implicit_param_refs_id, + .param_refs_id = param_refs_id, + .return_type_id = return_type_id, + .return_slot_id = return_slot_id}; + if (is_definition) { + function_info.definition_id = function_info.decl_id; + } // At interface scope, a function declaration introduces an associated // function. - auto lookup_result_id = function_decl_id; + auto lookup_result_id = function_info.decl_id; if (name_context.enclosing_scope_id_for_new_inst().is_valid() && !name_context.has_qualifiers) { auto scope_inst_id = context.name_scopes().GetInstIdIfValid( @@ -131,7 +143,7 @@ static auto BuildFunctionDecl(Context& context, context.insts().TryGetAsIfValid( scope_inst_id)) { lookup_result_id = BuildAssociatedEntity( - context, interface_scope->interface_id, function_decl_id); + context, interface_scope->interface_id, function_info.decl_id); } } @@ -141,44 +153,27 @@ static auto BuildFunctionDecl(Context& context, if (existing_id.is_valid()) { if (auto existing_function_decl = context.insts().Get(existing_id).TryAs()) { - // This is a redeclaration of an existing function. - function_decl.function_id = existing_function_decl->function_id; - - // TODO: Check that the signature matches! - // TODO: Disallow redeclarations within classes? - - // Track the signature from the definition, so that IDs in the body match - // IDs in the signature. - if (is_definition) { - auto& function_info = - context.functions().Get(function_decl.function_id); - function_info.implicit_param_refs_id = implicit_param_refs_id; - function_info.param_refs_id = param_refs_id; - function_info.return_type_id = return_type_id; - function_info.return_slot_id = return_slot_id; + if (MergeFunctionRedecl(context, parse_node, function_info, + existing_function_decl->function_id, + is_definition)) { + // When merging, use the existing function rather than adding a new one. + function_decl.function_id = existing_function_decl->function_id; } } else { - // This is a redeclaration of something other than a function. - // This includes the case where an associated function redeclares another + // This is a redeclaration of something other than a function. This + // includes the case where an associated function redeclares another // associated function. - context.DiagnoseDuplicateName(function_decl_id, existing_id); + context.DiagnoseDuplicateName(function_info.decl_id, existing_id); } } // Create a new function if this isn't a valid redeclaration. if (!function_decl.function_id.is_valid()) { - function_decl.function_id = context.functions().Add( - {.name_id = name_context.name_id_for_new_inst(), - .enclosing_scope_id = name_context.enclosing_scope_id_for_new_inst(), - .decl_id = function_decl_id, - .implicit_param_refs_id = implicit_param_refs_id, - .param_refs_id = param_refs_id, - .return_type_id = return_type_id, - .return_slot_id = return_slot_id}); + function_decl.function_id = context.functions().Add(function_info); } // Write the function ID into the FunctionDecl. - context.ReplaceInstBeforeConstantUse(function_decl_id, + context.ReplaceInstBeforeConstantUse(function_info.decl_id, {parse_node, function_decl}); if (SemIR::IsEntryPoint(context.sem_ir(), function_decl.function_id)) { @@ -196,7 +191,7 @@ static auto BuildFunctionDecl(Context& context, } } - return {function_decl.function_id, function_decl_id}; + return {function_decl.function_id, function_info.decl_id}; } auto HandleFunctionDecl(Context& context, Parse::FunctionDeclId parse_node) @@ -214,20 +209,6 @@ auto HandleFunctionDefinitionStart(Context& context, BuildFunctionDecl(context, parse_node, /*is_definition=*/true); auto& function = context.functions().Get(function_id); - // Track that this declaration is the definition. - if (function.definition_id.is_valid()) { - CARBON_DIAGNOSTIC(FunctionRedefinition, Error, - "Redefinition of function {0}.", SemIR::NameId); - CARBON_DIAGNOSTIC(FunctionPreviousDefinition, Note, - "Previous definition was here."); - context.emitter() - .Build(parse_node, FunctionRedefinition, function.name_id) - .Note(function.definition_id, FunctionPreviousDefinition) - .Emit(); - } else { - function.definition_id = decl_id; - } - // Create the function scope and the entry block. context.return_scope_stack().push_back({.decl_id = decl_id}); context.inst_block_stack().Push(); diff --git a/toolchain/check/testdata/class/fail_redefinition.carbon b/toolchain/check/testdata/class/fail_redefinition.carbon index 1cba19e0ff18..bf8816bd256c 100644 --- a/toolchain/check/testdata/class/fail_redefinition.carbon +++ b/toolchain/check/testdata/class/fail_redefinition.carbon @@ -17,6 +17,12 @@ class Class { // CHECK:STDERR: ^~~~~~~~~~~~~ class Class { fn G(); + // CHECK:STDERR: fail_redefinition.carbon:[[@LINE+6]]:3: ERROR: Redundant redeclaration of function H. + // CHECK:STDERR: fn H(); + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: fail_redefinition.carbon:[[@LINE-14]]:3: Previously declared here. + // CHECK:STDERR: fn H(); + // CHECK:STDERR: ^~~~~~~ fn H(); } diff --git a/toolchain/check/testdata/class/fail_self.carbon b/toolchain/check/testdata/class/fail_self.carbon index cd2e2d122b41..a8af4c5e98a6 100644 --- a/toolchain/check/testdata/class/fail_self.carbon +++ b/toolchain/check/testdata/class/fail_self.carbon @@ -72,9 +72,9 @@ fn CallWrongSelf(ws: WrongSelf) { // CHECK:STDOUT: %self.loc25_12.1: Class = param self // CHECK:STDOUT: @F.1.%self: Class = bind_name self, %self.loc25_12.1 // CHECK:STDOUT: } -// CHECK:STDOUT: %G: = fn_decl @G [template] { +// CHECK:STDOUT: %.loc28: = fn_decl @.1 [template] { // CHECK:STDOUT: %Self.ref.loc28: type = name_ref Self, constants.%Class [template = constants.%Class] -// CHECK:STDOUT: @G.%return: ref Class = var +// CHECK:STDOUT: @.1.%return: ref Class = var // CHECK:STDOUT: } // CHECK:STDOUT: %WrongSelf.decl = class_decl @WrongSelf [template = constants.%WrongSelf] {} // CHECK:STDOUT: %CallWrongSelf: = fn_decl @CallWrongSelf [template] { @@ -118,7 +118,9 @@ fn CallWrongSelf(ws: WrongSelf) { // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: fn @G() -> %return: Class { +// CHECK:STDOUT: fn @G() -> ; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.1() -> %return: Class { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%Class [template = constants.%Class] // CHECK:STDOUT: %self.var: ref Class = var self diff --git a/toolchain/check/testdata/function/declaration/fail_redecl.carbon b/toolchain/check/testdata/function/declaration/fail_redecl.carbon new file mode 100644 index 000000000000..e2274d12e4bc --- /dev/null +++ b/toolchain/check/testdata/function/declaration/fail_redecl.carbon @@ -0,0 +1,103 @@ +// 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 + +fn A(); +// CHECK:STDERR: fail_redecl.carbon:[[@LINE+6]]:1: ERROR: Redundant redeclaration of function A. +// CHECK:STDERR: fn A(); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_redecl.carbon:[[@LINE-4]]:1: Previously declared here. +// CHECK:STDERR: fn A(); +// CHECK:STDERR: ^~~~~~~ +fn A(); + +fn B(x: i32); +// CHECK:STDERR: fail_redecl.carbon:[[@LINE+6]]:1: ERROR: Redundant redeclaration of function B. +// CHECK:STDERR: fn B(x: i32); +// CHECK:STDERR: ^~~~~~~~~~~~~ +// CHECK:STDERR: fail_redecl.carbon:[[@LINE-4]]:1: Previously declared here. +// CHECK:STDERR: fn B(x: i32); +// CHECK:STDERR: ^~~~~~~~~~~~~ +fn B(x: i32); + +fn C(); +// CHECK:STDERR: fail_redecl.carbon:[[@LINE+6]]:1: ERROR: Function redeclaration differs because of parameter count of 1. +// CHECK:STDERR: fn C(x: i32); +// CHECK:STDERR: ^~~~~~~~~~~~~ +// CHECK:STDERR: fail_redecl.carbon:[[@LINE-4]]:1: Previously declared with parameter count of 0. +// CHECK:STDERR: fn C(); +// CHECK:STDERR: ^~~~~~~ +fn C(x: i32); + +fn D() {} +// CHECK:STDERR: fail_redecl.carbon:[[@LINE+6]]:1: ERROR: Redundant redeclaration of function D. +// CHECK:STDERR: fn D(); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_redecl.carbon:[[@LINE-4]]:1: Previously declared here. +// CHECK:STDERR: fn D() {} +// CHECK:STDERR: ^~~~~~~~ +fn D(); + +fn E() {} +// CHECK:STDERR: fail_redecl.carbon:[[@LINE+6]]:1: ERROR: Redefinition of function E. +// CHECK:STDERR: fn E() {} +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_redecl.carbon:[[@LINE-4]]:1: Previously defined here. +// CHECK:STDERR: fn E() {} +// CHECK:STDERR: ^~~~~~~~ +fn E() {} + +// CHECK:STDOUT: --- fail_redecl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .A = %A.loc7 +// CHECK:STDOUT: .B = %B.loc16 +// CHECK:STDOUT: .C = %C +// CHECK:STDOUT: .D = %D.loc34 +// CHECK:STDOUT: .E = %E.loc43 +// CHECK:STDOUT: } +// CHECK:STDOUT: %A.loc7: = fn_decl @A [template] {} +// CHECK:STDOUT: %A.loc14: = fn_decl @A [template] {} +// CHECK:STDOUT: %B.loc16: = fn_decl @B [template] { +// CHECK:STDOUT: %x.loc16_6.1: i32 = param x +// CHECK:STDOUT: @B.%x: i32 = bind_name x, %x.loc16_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %B.loc23: = fn_decl @B [template] { +// CHECK:STDOUT: %x.loc23_6.1: i32 = param x +// CHECK:STDOUT: %x.loc23_6.2: i32 = bind_name x, %x.loc23_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %C: = fn_decl @C [template] {} +// CHECK:STDOUT: %.loc32: = fn_decl @.1 [template] { +// CHECK:STDOUT: %x.loc32_6.1: i32 = param x +// CHECK:STDOUT: @.1.%x: i32 = bind_name x, %x.loc32_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %D.loc34: = fn_decl @D [template] {} +// CHECK:STDOUT: %D.loc41: = fn_decl @D [template] {} +// CHECK:STDOUT: %E.loc43: = fn_decl @E [template] {} +// CHECK:STDOUT: %E.loc50: = fn_decl @E [template] {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @A(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @B(%x: i32); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @C(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.1(%x: i32); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @D() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @E() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: +// CHECK:STDOUT: !.loc50: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/function/definition/fail_decl_param_mismatch.carbon b/toolchain/check/testdata/function/definition/fail_decl_param_mismatch.carbon new file mode 100644 index 000000000000..803b14aeebdb --- /dev/null +++ b/toolchain/check/testdata/function/definition/fail_decl_param_mismatch.carbon @@ -0,0 +1,154 @@ +// 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 + +fn F(); +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE+6]]:1: ERROR: Function redeclaration differs because of parameter count of 1. +// CHECK:STDERR: fn F(x: i32) {} +// CHECK:STDERR: ^~~~~~~~~~~~~~ +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE-4]]:1: Previously declared with parameter count of 0. +// CHECK:STDERR: fn F(); +// CHECK:STDERR: ^~~~~~~ +fn F(x: i32) {} + +fn G(x: i32); +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE+6]]:1: ERROR: Function redeclaration differs because of parameter count of 0. +// CHECK:STDERR: fn G() {} +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE-4]]:1: Previously declared with parameter count of 1. +// CHECK:STDERR: fn G(x: i32); +// CHECK:STDERR: ^~~~~~~~~~~~~ +fn G() {} + +fn H(x: i32); +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE+6]]:6: ERROR: Function redeclaration differs at parameter 1. +// CHECK:STDERR: fn H(x: bool) {} +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE-4]]:6: Previous declaration's corresponding parameter here. +// CHECK:STDERR: fn H(x: i32); +// CHECK:STDERR: ^ +fn H(x: bool) {} + +fn I(); +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE+6]]:1: ERROR: Function redeclaration differs because return type is `i32`. +// CHECK:STDERR: fn I() -> i32 { return 0; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE-4]]:1: Previously declared with no return type. +// CHECK:STDERR: fn I(); +// CHECK:STDERR: ^~~~~~~ +fn I() -> i32 { return 0; } + +fn J() -> i32; +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE+6]]:1: ERROR: Function redeclaration differs because no return type is provided. +// CHECK:STDERR: fn J() {} +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE-4]]:1: Previously declared with return type `i32`. +// CHECK:STDERR: fn J() -> i32; +// CHECK:STDERR: ^~~~~~~~~~~~~~ +fn J() {} + +fn K() -> i32; +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE+6]]:1: ERROR: Function redeclaration differs because return type is `bool`. +// CHECK:STDERR: fn K() -> bool { return false; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_decl_param_mismatch.carbon:[[@LINE-4]]:1: Previously declared with return type `i32`. +// CHECK:STDERR: fn K() -> i32; +// CHECK:STDERR: ^~~~~~~~~~~~~~ +fn K() -> bool { return false; } + +// CHECK:STDOUT: --- fail_decl_param_mismatch.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: i32 = int_literal 0 [template] +// CHECK:STDOUT: %.2: bool = bool_literal false [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: .H = %H +// CHECK:STDOUT: .I = %I +// CHECK:STDOUT: .J = %J +// CHECK:STDOUT: .K = %K +// CHECK:STDOUT: } +// CHECK:STDOUT: %F: = fn_decl @F [template] {} +// CHECK:STDOUT: %.loc14: = fn_decl @.1 [template] { +// CHECK:STDOUT: %x.loc14_6.1: i32 = param x +// CHECK:STDOUT: @.1.%x: i32 = bind_name x, %x.loc14_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %G: = fn_decl @G [template] { +// CHECK:STDOUT: %x.loc16_6.1: i32 = param x +// CHECK:STDOUT: @G.%x: i32 = bind_name x, %x.loc16_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc23: = fn_decl @.2 [template] {} +// CHECK:STDOUT: %H: = fn_decl @H [template] { +// CHECK:STDOUT: %x.loc25_6.1: i32 = param x +// CHECK:STDOUT: @H.%x: i32 = bind_name x, %x.loc25_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc32: = fn_decl @.3 [template] { +// CHECK:STDOUT: %x.loc32_6.1: bool = param x +// CHECK:STDOUT: @.3.%x: bool = bind_name x, %x.loc32_6.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %I: = fn_decl @I [template] {} +// CHECK:STDOUT: %.loc41: = fn_decl @.4 [template] { +// CHECK:STDOUT: %return.var.loc41: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: %J: = fn_decl @J [template] { +// CHECK:STDOUT: %return.var.loc43: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc50: = fn_decl @.5 [template] {} +// CHECK:STDOUT: %K: = fn_decl @K [template] { +// CHECK:STDOUT: %return.var.loc52: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc59: = fn_decl @.6 [template] { +// CHECK:STDOUT: %return.var.loc59: ref bool = var +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.1(%x: i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%x: i32); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.2() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H(%x: i32); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.3(%x: bool) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @I(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.4() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc41: i32 = int_literal 0 [template = constants.%.1] +// CHECK:STDOUT: return %.loc41 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @J() -> i32; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.5() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @K() -> i32; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @.6() -> bool { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc59: bool = bool_literal false [template = constants.%.2] +// CHECK:STDOUT: return %.loc59 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/function/definition/fail_redef.carbon b/toolchain/check/testdata/function/definition/fail_redef.carbon new file mode 100644 index 000000000000..57728cfa7af0 --- /dev/null +++ b/toolchain/check/testdata/function/definition/fail_redef.carbon @@ -0,0 +1,33 @@ +// 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 + +fn F() {} +// CHECK:STDERR: fail_redef.carbon:[[@LINE+6]]:1: ERROR: Redefinition of function F. +// CHECK:STDERR: fn F() {} +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_redef.carbon:[[@LINE-4]]:1: Previously defined here. +// CHECK:STDERR: fn F() {} +// CHECK:STDERR: ^~~~~~~~ +fn F() {} + +// CHECK:STDOUT: --- fail_redef.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .F = %F.loc7 +// CHECK:STDOUT: } +// CHECK:STDOUT: %F.loc7: = fn_decl @F [template] {} +// CHECK:STDOUT: %F.loc14: = fn_decl @F [template] {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: +// CHECK:STDOUT: !.loc14: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/namespace/fail_duplicate.carbon b/toolchain/check/testdata/namespace/fail_duplicate.carbon index 4d8c825666ea..ba878b5333c7 100644 --- a/toolchain/check/testdata/namespace/fail_duplicate.carbon +++ b/toolchain/check/testdata/namespace/fail_duplicate.carbon @@ -12,7 +12,7 @@ fn Foo.Baz() { // CHECK:STDERR: fail_duplicate.carbon:[[@LINE+6]]:1: ERROR: Redefinition of function Baz. // CHECK:STDERR: fn Foo.Baz() { // CHECK:STDERR: ^~~~~~~~~~~~~~ -// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-6]]:1: Previous definition was here. +// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-6]]:1: Previously defined here. // CHECK:STDERR: fn Foo.Baz() { // CHECK:STDERR: ^~~~~~~~~~~~~~ fn Foo.Baz() { diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 0a331d2825f1..b587d876f982 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -153,6 +153,22 @@ CARBON_DIAGNOSTIC_KIND(InCallToFunctionParam) CARBON_DIAGNOSTIC_KIND(InCallToFunctionSelf) CARBON_DIAGNOSTIC_KIND(MissingObjectInMethodCall) +// Function declaration checking. +CARBON_DIAGNOSTIC_KIND(FunctionPreviousDecl) +CARBON_DIAGNOSTIC_KIND(FunctionRedecl) +CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition) +CARBON_DIAGNOSTIC_KIND(FunctionRedefinition) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclParamCountDiffers) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclParamCountPrevious) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclParamDiffers) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclParamPrevious) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypeDiffers) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypeDiffersNoReturn) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypePrevious) +CARBON_DIAGNOSTIC_KIND(FunctionRedeclReturnTypePreviousNoReturn) +CARBON_DIAGNOSTIC_KIND(InvalidMainRunSignature) +CARBON_DIAGNOSTIC_KIND(MissingReturnStatement) + // Class checking. CARBON_DIAGNOSTIC_KIND(BaseIsFinal) CARBON_DIAGNOSTIC_KIND(BaseMissingExtend) @@ -200,14 +216,11 @@ CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop) CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType) CARBON_DIAGNOSTIC_KIND(DerefOfNonPointer) CARBON_DIAGNOSTIC_KIND(DerefOfType) -CARBON_DIAGNOSTIC_KIND(FunctionPreviousDefinition) -CARBON_DIAGNOSTIC_KIND(FunctionRedefinition) CARBON_DIAGNOSTIC_KIND(CompileTimeBindingInVarDecl) CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend) CARBON_DIAGNOSTIC_KIND(NameNotFound) CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate) CARBON_DIAGNOSTIC_KIND(NameDeclPrevious) -CARBON_DIAGNOSTIC_KIND(MissingReturnStatement) CARBON_DIAGNOSTIC_KIND(RepeatedConst) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInBaseDecl) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInConversion) @@ -220,7 +233,6 @@ CARBON_DIAGNOSTIC_KIND(IncompleteTypeInValueConversion) CARBON_DIAGNOSTIC_KIND(IncompleteTypeInVarDecl) CARBON_DIAGNOSTIC_KIND(InvalidArrayExpr) CARBON_DIAGNOSTIC_KIND(TypeNotIndexable) -CARBON_DIAGNOSTIC_KIND(InvalidMainRunSignature) CARBON_DIAGNOSTIC_KIND(SelfOutsideImplicitParamList) CARBON_DIAGNOSTIC_KIND(StructInitElementCountMismatch) CARBON_DIAGNOSTIC_KIND(StructInitMissingFieldInLiteral)