Files
carbon-lang/toolchain/check/handle_operator.cpp
T
Richard Smithandjosh11b b274622228 Improve infrastructure for formatting types in diagnostics. (#4374)
Instead of stringifying types in the caller in some cases, add new types
to represent:

- `InstIdAsType`: an `InstId` diagnostic argument that represents a type
expression that should be included in the diagnostic
- `InstIdAsTypeOfExpr`: an `InstId` diagnostic argument that represents
an expression whose type should be included in the diagnostic

For these cases, we can produce more user-friendly descriptions of a
type than we can with a canonicalized `TypeId`. Add comments to
discourage using `TypeId` diagnostic arguments when one of the above can
be used, and move over existing uses where it's straightforward to do
so.

Move type stringification code to its own files and out of `SemIR::File`
to make `File` smaller and to further discourage the direct use of the
stringification logic.

Also update type printing to include the `` ` `` delimiters surrounding
the type. The intent is that we will eventually want to include other
information when formatting a type, like Clang does when printing a
typedef (`'string' (aka 'std::basic_string<char>')`), and such
formatting requires that the diagnostic machinery produces the `` ` ``s
itself.

There are a couple of cases where we really want to format valid Carbon
type syntax directly into a diagnostic, rather than an `aka` or similar,
because the diagnostic text includes part of the type itself, for
example: ``"consider using `partial {0}`"``. For such cases, a `Raw`
form of the diagnostic argument types is added: `TypeIdAsRawType` and
`InstIdAsRawType`. In principle we could instead use ``"consider using
`partial {0:raw}`"``, but our diagnostic machinery isn't set up for
that.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
2024-10-07 22:55:26 +00:00

424 lines
16 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/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/pointer_dereference.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
namespace Carbon::Check {
// Common logic for unary operator handlers.
static auto HandleUnaryOperator(Context& context, Parse::AnyExprId expr_node_id,
Operator op) -> bool {
auto operand_id = context.node_stack().PopExpr();
auto result_id = BuildUnaryOperator(context, expr_node_id, op, operand_id);
context.node_stack().Push(expr_node_id, result_id);
return true;
}
// Common logic for binary operator handlers.
static auto HandleBinaryOperator(Context& context,
Parse::AnyExprId expr_node_id, Operator op)
-> bool {
auto rhs_id = context.node_stack().PopExpr();
auto lhs_id = context.node_stack().PopExpr();
auto result_id =
BuildBinaryOperator(context, expr_node_id, op, lhs_id, rhs_id);
context.node_stack().Push(expr_node_id, result_id);
return true;
}
auto HandleParseNode(Context& context, Parse::InfixOperatorAmpId node_id)
-> bool {
// TODO: Facet type intersection may need to be handled directly.
return HandleBinaryOperator(context, node_id, {"BitAnd"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorAmpEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"BitAndAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorAsId node_id)
-> bool {
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId();
auto rhs_type_id = ExprAsType(context, rhs_node, rhs_id).type_id;
context.node_stack().Push(
node_id, ConvertForExplicitAs(context, node_id, lhs_id, rhs_type_id));
return true;
}
auto HandleParseNode(Context& context, Parse::InfixOperatorCaretId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"BitXor"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorCaretEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"BitXorAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id)
-> bool {
// TODO: Switch to using assignment interface for most assignment. Some cases
// may need to be handled directly.
//
// return HandleBinaryOperator(context, node_id, {"Assign"});
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId();
if (auto lhs_cat = SemIR::GetExprCategory(context.sem_ir(), lhs_id);
lhs_cat != SemIR::ExprCategory::DurableRef &&
lhs_cat != SemIR::ExprCategory::Error) {
CARBON_DIAGNOSTIC(AssignmentToNonAssignable, Error,
"expression is not assignable");
context.emitter().Emit(lhs_node, AssignmentToNonAssignable);
}
// TODO: Destroy the old value before reinitializing. This will require
// building the destruction code before we build the RHS subexpression.
rhs_id = Initialize(context, node_id, lhs_id, rhs_id);
context.AddInst<SemIR::Assign>(node_id, {.lhs_id = lhs_id, .rhs_id = rhs_id});
// We model assignment as an expression, so we need to push a value for
// it, even though it doesn't produce a value.
// TODO: Consider changing our parse tree to model assignment as a
// different kind of statement than an expression statement.
context.node_stack().Push(node_id, lhs_id);
return true;
}
auto HandleParseNode(Context& context, Parse::InfixOperatorEqualEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Eq", {}, "Equal"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorExclaimEqualId node_id) -> bool {
return HandleBinaryOperator(context, node_id, {"Eq", {}, "NotEqual"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorGreaterId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Ordered", {}, "Greater"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorGreaterEqualId node_id) -> bool {
return HandleBinaryOperator(context, node_id,
{"Ordered", {}, "GreaterOrEquivalent"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorGreaterGreaterId node_id) -> bool {
return HandleBinaryOperator(context, node_id, {"RightShift"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorGreaterGreaterEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"RightShiftAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorLessId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Ordered", {}, "Less"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorLessEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id,
{"Ordered", {}, "LessOrEquivalent"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorLessEqualGreaterId node_id) -> bool {
return context.TODO(node_id, "remove <=> operator that is not in the design");
}
auto HandleParseNode(Context& context, Parse::InfixOperatorLessLessId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"LeftShift"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorLessLessEqualId node_id) -> bool {
return HandleBinaryOperator(context, node_id, {"LeftShiftAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorMinusId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Sub"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorMinusEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"SubAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorPercentId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Mod"});
}
auto HandleParseNode(Context& context,
Parse::InfixOperatorPercentEqualId node_id) -> bool {
return HandleBinaryOperator(context, node_id, {"ModAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorPipeId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"BitOr"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorPipeEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"BitOrAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorPlusId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Add"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorPlusEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"AddAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorSlashId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Div"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorSlashEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"DivAssign"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorStarId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"Mul"});
}
auto HandleParseNode(Context& context, Parse::InfixOperatorStarEqualId node_id)
-> bool {
return HandleBinaryOperator(context, node_id, {"MulAssign"});
}
auto HandleParseNode(Context& context, Parse::PostfixOperatorStarId node_id)
-> bool {
auto value_id = context.node_stack().PopExpr();
auto inner_type_id = ExprAsType(context, node_id, value_id).type_id;
context.AddInstAndPush<SemIR::PointerType>(
node_id,
{.type_id = SemIR::TypeId::TypeType, .pointee_id = inner_type_id});
return true;
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorAmpId node_id)
-> bool {
auto value_id = context.node_stack().PopExpr();
auto type_id = context.insts().Get(value_id).type_id();
// Only durable reference expressions can have their address taken.
switch (SemIR::GetExprCategory(context.sem_ir(), value_id)) {
case SemIR::ExprCategory::DurableRef:
case SemIR::ExprCategory::Error:
break;
case SemIR::ExprCategory::EphemeralRef:
CARBON_DIAGNOSTIC(AddrOfEphemeralRef, Error,
"cannot take the address of a temporary object");
context.emitter().Emit(TokenOnly(node_id), AddrOfEphemeralRef);
value_id = SemIR::InstId::BuiltinError;
break;
default:
CARBON_DIAGNOSTIC(AddrOfNonRef, Error,
"cannot take the address of non-reference expression");
context.emitter().Emit(TokenOnly(node_id), AddrOfNonRef);
value_id = SemIR::InstId::BuiltinError;
break;
}
context.AddInstAndPush<SemIR::AddrOf>(
node_id, SemIR::AddrOf{.type_id = context.GetPointerType(type_id),
.lvalue_id = value_id});
return true;
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorCaretId node_id)
-> bool {
return HandleUnaryOperator(context, node_id, {"BitComplement"});
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorConstId node_id)
-> bool {
auto value_id = context.node_stack().PopExpr();
// `const (const T)` is probably not what the developer intended.
// TODO: Detect `const (const T)*` and suggest moving the `*` inside the
// parentheses.
if (context.insts().Get(value_id).kind() == SemIR::ConstType::Kind) {
CARBON_DIAGNOSTIC(RepeatedConst, Warning,
"`const` applied repeatedly to the same type has no "
"additional effect");
context.emitter().Emit(node_id, RepeatedConst);
}
auto inner_type_id = ExprAsType(context, node_id, value_id).type_id;
context.AddInstAndPush<SemIR::ConstType>(
node_id, {.type_id = SemIR::TypeId::TypeType, .inner_id = inner_type_id});
return true;
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorMinusId node_id)
-> bool {
return HandleUnaryOperator(context, node_id, {"Negate"});
}
auto HandleParseNode(Context& context,
Parse::PrefixOperatorMinusMinusId node_id) -> bool {
return HandleUnaryOperator(context, node_id, {"Dec"});
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorNotId node_id)
-> bool {
auto value_id = context.node_stack().PopExpr();
value_id = ConvertToBoolValue(context, node_id, value_id);
context.AddInstAndPush<SemIR::UnaryOperatorNot>(
node_id, {.type_id = context.insts().Get(value_id).type_id(),
.operand_id = value_id});
return true;
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorPlusPlusId node_id)
-> bool {
return HandleUnaryOperator(context, node_id, {"Inc"});
}
auto HandleParseNode(Context& context, Parse::PrefixOperatorStarId node_id)
-> bool {
auto base_id = context.node_stack().PopExpr();
auto deref_base_id = PerformPointerDereference(
context, node_id, base_id,
[&context, &node_id](SemIR::TypeId not_pointer_type_id) {
// TODO: Pass in the expression we're trying to dereference to produce a
// better diagnostic.
CARBON_DIAGNOSTIC(DerefOfNonPointer, Error,
"cannot dereference operand of non-pointer type {0}",
SemIR::TypeId);
auto builder = context.emitter().Build(
TokenOnly(node_id), DerefOfNonPointer, not_pointer_type_id);
// TODO: Check for any facet here, rather than only a type.
if (not_pointer_type_id == SemIR::TypeId::TypeType) {
CARBON_DIAGNOSTIC(
DerefOfType, Note,
"to form a pointer type, write the `*` after the pointee type");
builder.Note(TokenOnly(node_id), DerefOfType);
}
builder.Emit();
});
context.node_stack().Push(node_id, deref_base_id);
return true;
}
// Adds the branch for a short circuit operand.
static auto HandleShortCircuitOperand(Context& context, Parse::NodeId node_id,
bool is_or) -> bool {
// Convert the condition to `bool`.
auto [cond_node, cond_value_id] = context.node_stack().PopExprWithNodeId();
cond_value_id = ConvertToBoolValue(context, node_id, cond_value_id);
auto bool_type_id = context.insts().Get(cond_value_id).type_id();
// Compute the branch value: the condition for `and`, inverted for `or`.
SemIR::InstId branch_value_id =
is_or
? context.AddInst<SemIR::UnaryOperatorNot>(
node_id, {.type_id = bool_type_id, .operand_id = cond_value_id})
: cond_value_id;
auto short_circuit_result_id = context.AddInst<SemIR::BoolLiteral>(
node_id,
{.type_id = bool_type_id, .value = SemIR::BoolValue::From(is_or)});
// Create a block for the right-hand side and for the continuation.
auto rhs_block_id =
context.AddDominatedBlockAndBranchIf(node_id, branch_value_id);
auto end_block_id = context.AddDominatedBlockAndBranchWithArg(
node_id, short_circuit_result_id);
// Push the branch condition and result for use when handling the complete
// expression.
context.node_stack().Push(cond_node, branch_value_id);
context.node_stack().Push(cond_node, short_circuit_result_id);
// Push the resumption and the right-hand side blocks, and start emitting the
// right-hand operand.
context.inst_block_stack().Pop();
context.inst_block_stack().Push(end_block_id);
context.inst_block_stack().Push(rhs_block_id);
context.AddCurrentCodeBlockToFunction(node_id);
// HandleShortCircuitOperator will follow, and doesn't need the operand on the
// node stack.
return true;
}
auto HandleParseNode(Context& context, Parse::ShortCircuitOperandAndId node_id)
-> bool {
return HandleShortCircuitOperand(context, node_id, /*is_or=*/false);
}
auto HandleParseNode(Context& context, Parse::ShortCircuitOperandOrId node_id)
-> bool {
return HandleShortCircuitOperand(context, node_id, /*is_or=*/true);
}
// Short circuit operator handling is uniform because the branching logic
// occurs during operand handling.
static auto HandleShortCircuitOperator(Context& context, Parse::NodeId node_id)
-> bool {
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
auto short_circuit_result_id = context.node_stack().PopExpr();
auto branch_value_id = context.node_stack().PopExpr();
// The first operand is wrapped in a ShortCircuitOperand, which we
// already handled by creating a RHS block and a resumption block, which
// are the current block and its enclosing block.
rhs_id = ConvertToBoolValue(context, node_id, rhs_id);
// When the second operand is evaluated, the result of `and` and `or` is
// its value.
auto resume_block_id = context.inst_block_stack().PeekOrAdd(/*depth=*/1);
context.AddInst<SemIR::BranchWithArg>(
node_id, {.target_id = resume_block_id, .arg_id = rhs_id});
context.inst_block_stack().Pop();
context.AddCurrentCodeBlockToFunction(node_id);
// Collect the result from either the first or second operand.
auto result_id = context.AddInst<SemIR::BlockArg>(
node_id, {.type_id = context.insts().Get(rhs_id).type_id(),
.block_id = resume_block_id});
context.SetBlockArgResultBeforeConstantUse(result_id, branch_value_id, rhs_id,
short_circuit_result_id);
context.node_stack().Push(node_id, result_id);
return true;
}
auto HandleParseNode(Context& context, Parse::ShortCircuitOperatorAndId node_id)
-> bool {
return HandleShortCircuitOperator(context, node_id);
}
auto HandleParseNode(Context& context, Parse::ShortCircuitOperatorOrId node_id)
-> bool {
return HandleShortCircuitOperator(context, node_id);
}
} // namespace Carbon::Check