mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Introduces `Context` and `SoftContext` messages, which can be introduced
through a `ContextBuilder`:
- The `Context` messages come before the diagnostic in the output.
- The first `Context` message steals the diagnostic level from the main
diagnostic, and turns the main diagnostic into a Note attached to the
context.
- A `SoftContext` message works similarly, but if it's preceeded by a
`Context` or `SoftContext` message, then it is dropped. This can be used
as a default/backup scope when nothing more interesting is provided up
the stack, such as in `TryEvalBlockForSpecific`.
The `ContextBuilder` is provided to a callback through
`Diagnostics::ContextScope`, an RAII type `AnnotationScope` but for
context messages.
This allows a high level operation to provide a context message like
"failed to identify facet type {0}" which will then be used as the error
if a diagnostic is produced during identification, with the latter
diagnostic attached as a note to explain why the contextual operation
failed.
In particular, this allows monomorphization errors (such as an array
bound being negative) to be attached to a higher lever operation instead
of being top-level diagnostics themselves, with the monomorphization
site being a note. This inverts the source code locations that appear in
the diagnostic, so that the top-level diagnostic points to the "user
code" which causes the monomorphization.
This is presented as an alternative strategy to #6753, which plumbed
diagnoser callbacks around to achieve the same goals.
We replace the diagnoser callbacks in type completion and operators with
ContextScope callbacks instead, which now provide better diagnostics for
monomorphization errors. Other callers to MakeSpecific do not yet have
ContextScopes introduced in order to turn monomorphization errors into
more interesting diagnostics.
165 lines
6.5 KiB
C++
165 lines
6.5 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 "toolchain/check/operator.h"
|
|
|
|
#include <optional>
|
|
|
|
#include "toolchain/check/call.h"
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/cpp/call.h"
|
|
#include "toolchain/check/cpp/operators.h"
|
|
#include "toolchain/check/generic.h"
|
|
#include "toolchain/check/member_access.h"
|
|
#include "toolchain/check/name_lookup.h"
|
|
#include "toolchain/sem_ir/class.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/sem_ir/name_scope.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// Returns the `Op` function for the specified operator.
|
|
static auto GetOperatorOpFunction(Context& context, SemIR::LocId loc_id,
|
|
Operator op) -> SemIR::InstId {
|
|
auto implicit_loc_id = context.insts().GetLocIdForDesugaring(loc_id);
|
|
|
|
// Look up the interface, and pass it any generic arguments.
|
|
// TODO: Improve diagnostics when the found `interface_id` isn't callable.
|
|
auto interface_id =
|
|
LookupNameInCore(context, implicit_loc_id, op.interface_name);
|
|
if (!op.interface_args_ref.empty()) {
|
|
interface_id = PerformCall(context, implicit_loc_id, interface_id,
|
|
op.interface_args_ref);
|
|
}
|
|
|
|
// Look up the interface member.
|
|
auto op_name_id = context.core_identifiers().AddNameId(op.op_name);
|
|
return PerformMemberAccess(context, implicit_loc_id, interface_id,
|
|
op_name_id);
|
|
}
|
|
|
|
// Returns whether the instruction is a C++ class type. Assumes the argument is
|
|
// in canonical form and does not look through the constant value.
|
|
static auto IsCppClassType(Context& context, SemIR::InstId inst_id) -> bool {
|
|
auto class_type = context.insts().TryGetAs<SemIR::ClassType>(inst_id);
|
|
if (!class_type) {
|
|
// Not a class.
|
|
return false;
|
|
}
|
|
|
|
SemIR::NameScopeId class_scope_id =
|
|
context.classes().Get(class_type->class_id).scope_id;
|
|
return class_scope_id.has_value() &&
|
|
context.name_scopes().Get(class_scope_id).is_cpp_scope();
|
|
}
|
|
|
|
// Returns whether the instruction is a value of C++ class type.
|
|
static auto HasCppClassType(Context& context, SemIR::InstId inst_id) -> bool {
|
|
return IsCppClassType(context, context.types().GetTypeInstId(
|
|
context.insts().Get(inst_id).type_id()));
|
|
}
|
|
|
|
auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
|
SemIR::InstId operand_id, bool diagnose,
|
|
DiagnosticContextFn missing_impl_diagnostic_context)
|
|
-> SemIR::InstId {
|
|
if (operand_id == SemIR::ErrorInst::InstId) {
|
|
// Exit early for errors, which prevent forming an `Op` function.
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
|
|
SemIR::InstId op_fn_id = SemIR::InstId::None;
|
|
|
|
// For unary operators with a C++ class as the operand, try to import and call
|
|
// the C++ operator.
|
|
// TODO: Change impl lookup instead. See
|
|
// https://github.com/carbon-language/carbon-lang/blob/db0a00d713015436844c55e7ac190a0f95556499/toolchain/check/operator.cpp#L76
|
|
if (HasCppClassType(context, operand_id) ||
|
|
llvm::any_of(op.interface_args_ref, [&](SemIR::InstId arg_id) {
|
|
return IsCppClassType(context, arg_id);
|
|
})) {
|
|
op_fn_id = LookupCppOperator(context, loc_id, op, {operand_id});
|
|
|
|
// If C++ operator lookup found a non-method operator, call it with one call
|
|
// argument. Otherwise fall through to call it with a self argument.
|
|
if (op_fn_id.has_value() && !IsCppOperatorMethod(context, op_fn_id)) {
|
|
return PerformCall(context, loc_id, op_fn_id, {operand_id},
|
|
/*is_operator_syntax=*/true);
|
|
}
|
|
}
|
|
|
|
if (!op_fn_id.has_value()) {
|
|
// Look up the operator function.
|
|
op_fn_id = GetOperatorOpFunction(context, loc_id, op);
|
|
}
|
|
|
|
// Form `operand.(Op)`.
|
|
auto bound_op_id =
|
|
PerformCompoundMemberAccess(context, loc_id, operand_id, op_fn_id,
|
|
diagnose, missing_impl_diagnostic_context);
|
|
if (bound_op_id == SemIR::ErrorInst::InstId) {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
|
|
// Form `bound_op()`.
|
|
return PerformCall(context, loc_id, bound_op_id, {},
|
|
/*is_operator_syntax=*/true);
|
|
}
|
|
|
|
auto BuildBinaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
|
SemIR::InstId lhs_id, SemIR::InstId rhs_id,
|
|
bool diagnose,
|
|
DiagnosticContextFn missing_impl_diagnostic_context)
|
|
-> SemIR::InstId {
|
|
if (lhs_id == SemIR::ErrorInst::InstId) {
|
|
// Exit early for errors, which prevent forming an `Op` function.
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
|
|
SemIR::InstId op_fn_id = SemIR::InstId::None;
|
|
|
|
// For binary operators with a C++ class as at least one of the operands, try
|
|
// to import and call the C++ operator.
|
|
// TODO: Instead of hooking this here, change impl lookup, so that a generic
|
|
// constraint such as `T:! Core.Add` is satisfied by C++ class types that are
|
|
// addable. See
|
|
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308666348
|
|
// and
|
|
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308664536
|
|
if (HasCppClassType(context, lhs_id) || HasCppClassType(context, rhs_id) ||
|
|
llvm::any_of(op.interface_args_ref, [&](SemIR::InstId arg_id) {
|
|
return IsCppClassType(context, arg_id);
|
|
})) {
|
|
op_fn_id = LookupCppOperator(context, loc_id, op, {lhs_id, rhs_id});
|
|
|
|
// If C++ operator lookup found a non-method operator, call it with two call
|
|
// arguments. Otherwise fall through to call it with a self argument and one
|
|
// call argument.
|
|
if (op_fn_id.has_value() && !IsCppOperatorMethod(context, op_fn_id)) {
|
|
return PerformCall(context, loc_id, op_fn_id, {lhs_id, rhs_id},
|
|
/*is_operator_syntax=*/true);
|
|
}
|
|
}
|
|
|
|
if (!op_fn_id.has_value()) {
|
|
// Look up the operator function.
|
|
op_fn_id = GetOperatorOpFunction(context, loc_id, op);
|
|
}
|
|
|
|
// Form `lhs.(Op)`.
|
|
auto bound_op_id =
|
|
PerformCompoundMemberAccess(context, loc_id, lhs_id, op_fn_id, diagnose,
|
|
missing_impl_diagnostic_context);
|
|
if (bound_op_id == SemIR::ErrorInst::InstId) {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
|
|
// Form `bound_op(rhs)`.
|
|
return PerformCall(context, loc_id, bound_op_id, {rhs_id},
|
|
/*is_operator_syntax=*/true);
|
|
}
|
|
|
|
} // namespace Carbon::Check
|