Files
carbon-lang/toolchain/semantics/semantics_handle_operator.cpp
T
Richard SmithandChandler Carruth 1013d1773c Model function calls as initializing expressions (#3089)
Start treating function calls as initializing expressions instead of as
value expressions.

This required adding support for expression categories. Value bindings
and temporary materialization conversions are created where necessary to
transition between expression categories. For a function call with a
return slot, we speculatively create a materialized temporary before the
call and either commit to it or replace it with something else later,
once we see how the function call expression is actually used.

This change follows the direction suggested in #3133 for initializing
expressions: depending on the return type of a function, the return
value will either be initialized in-place or returned directly. This is
visible in the semantics IR, which is a little unfortunate but is
probably necessary as this is part of the semantics of the program.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2023-08-24 19:35:07 +00:00

242 lines
9.7 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/semantics/semantics_context.h"
namespace Carbon::Check {
auto HandleInfixOperator(Context& context, ParseTree::Node parse_node) -> bool {
auto rhs_id = context.node_stack().PopExpression();
auto [lhs_node, lhs_id] = context.node_stack().PopExpressionWithParseNode();
// Figure out the operator for the token.
auto token = context.parse_tree().node_token(parse_node);
switch (auto token_kind = context.tokens().GetKind(token)) {
case TokenKind::Plus:
// TODO: This should search for a compatible interface. For now, it's a
// very trivial check of validity on the operation.
lhs_id = context.ConvertToValueOfType(
parse_node, lhs_id, context.semantics_ir().GetNode(rhs_id).type_id());
rhs_id = context.ConvertToValueExpression(rhs_id);
context.AddNodeAndPush(
parse_node,
SemIR::Node::BinaryOperatorAdd::Make(
parse_node, context.semantics_ir().GetNode(lhs_id).type_id(),
lhs_id, rhs_id));
return true;
case TokenKind::And:
case TokenKind::Or: {
// 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 = context.ConvertToBoolValue(parse_node, rhs_id);
// When the second operand is evaluated, the result of `and` and `or` is
// its value.
auto rhs_block_id = context.node_block_stack().PopForAdd();
auto resume_block_id = context.node_block_stack().PeekForAdd();
context.AddNodeToBlock(rhs_block_id,
SemIR::Node::BranchWithArg::Make(
parse_node, resume_block_id, rhs_id));
context.AddCurrentCodeBlockToFunction();
// Collect the result from either the first or second operand.
context.AddNodeAndPush(
parse_node,
SemIR::Node::BlockArg::Make(
parse_node, context.semantics_ir().GetNode(rhs_id).type_id(),
resume_block_id));
return true;
}
case TokenKind::Equal: {
// TODO: handle complex assignment expression such as `a += 1`.
if (SemIR::GetExpressionCategory(context.semantics_ir(), lhs_id) !=
SemIR::ExpressionCategory::DurableReference) {
CARBON_DIAGNOSTIC(AssignmentToNonAssignable, Error,
"Expression is not assignable.");
context.emitter().Emit(lhs_node, AssignmentToNonAssignable);
}
context.Initialize(parse_node, lhs_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(parse_node, lhs_id);
return true;
}
default:
return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
}
}
auto HandlePostfixOperator(Context& context, ParseTree::Node parse_node)
-> bool {
auto value_id = context.node_stack().PopExpression();
// Figure out the operator for the token.
auto token = context.parse_tree().node_token(parse_node);
switch (auto token_kind = context.tokens().GetKind(token)) {
case TokenKind::Star: {
auto inner_type_id = context.ExpressionAsType(parse_node, value_id);
context.AddNodeAndPush(
parse_node, SemIR::Node::PointerType::Make(
parse_node, SemIR::TypeId::TypeType, inner_type_id));
return true;
}
default:
CARBON_FATAL() << "Unexpected postfix operator " << token_kind;
}
}
auto HandlePrefixOperator(Context& context, ParseTree::Node parse_node)
-> bool {
auto value_id = context.node_stack().PopExpression();
// Figure out the operator for the token.
auto token = context.parse_tree().node_token(parse_node);
switch (auto token_kind = context.tokens().GetKind(token)) {
case TokenKind::Amp: {
// Only durable reference expressions can have their address taken.
switch (SemIR::GetExpressionCategory(context.semantics_ir(), value_id)) {
case SemIR::ExpressionCategory::DurableReference:
break;
case SemIR::ExpressionCategory::EphemeralReference:
CARBON_DIAGNOSTIC(AddressOfEphemeralReference, Error,
"Cannot take the address of a temporary object.");
context.emitter().Emit(parse_node, AddressOfEphemeralReference);
break;
default:
CARBON_DIAGNOSTIC(
AddressOfNonReference, Error,
"Cannot take the address of non-reference expression.");
context.emitter().Emit(parse_node, AddressOfNonReference);
break;
}
context.AddNodeAndPush(
parse_node,
SemIR::Node::AddressOf::Make(
parse_node,
context.GetPointerType(
parse_node,
context.semantics_ir().GetNode(value_id).type_id()),
value_id));
return true;
}
case TokenKind::Const: {
// `const (const T)` is probably not what the developer intended.
// TODO: Detect `const (const T)*` and suggest moving the `*` inside the
// parentheses.
if (context.semantics_ir().GetNode(value_id).kind() ==
SemIR::NodeKind::ConstType) {
CARBON_DIAGNOSTIC(RepeatedConst, Warning,
"`const` applied repeatedly to the same type has no "
"additional effect.");
context.emitter().Emit(parse_node, RepeatedConst);
}
auto inner_type_id = context.ExpressionAsType(parse_node, value_id);
context.AddNodeAndPush(
parse_node, SemIR::Node::ConstType::Make(
parse_node, SemIR::TypeId::TypeType, inner_type_id));
return true;
}
case TokenKind::Not:
value_id = context.ConvertToBoolValue(parse_node, value_id);
context.AddNodeAndPush(
parse_node,
SemIR::Node::UnaryOperatorNot::Make(
parse_node, context.semantics_ir().GetNode(value_id).type_id(),
value_id));
return true;
case TokenKind::Star: {
auto type_id = context.GetUnqualifiedType(
context.semantics_ir().GetNode(value_id).type_id());
auto type_node = context.semantics_ir().GetNode(
context.semantics_ir().GetTypeAllowBuiltinTypes(type_id));
auto result_type_id = SemIR::TypeId::Error;
if (type_node.kind() == SemIR::NodeKind::PointerType) {
result_type_id = type_node.GetAsPointerType();
} else {
CARBON_DIAGNOSTIC(
DereferenceOfNonPointer, Error,
"Cannot dereference operand of non-pointer type `{0}`.",
std::string);
auto builder = context.emitter().Build(
parse_node, DereferenceOfNonPointer,
context.semantics_ir().StringifyType(type_id));
// TODO: Check for any facet here, rather than only a type.
if (type_id == SemIR::TypeId::TypeType) {
CARBON_DIAGNOSTIC(
DereferenceOfType, Note,
"To form a pointer type, write the `*` after the pointee type.");
builder.Note(parse_node, DereferenceOfType);
}
builder.Emit();
}
value_id = context.ConvertToValueExpression(value_id);
context.AddNodeAndPush(
parse_node,
SemIR::Node::Dereference::Make(parse_node, result_type_id, value_id));
return true;
}
default:
return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
}
}
auto HandleShortCircuitOperand(Context& context, ParseTree::Node parse_node)
-> bool {
// Convert the condition to `bool`.
auto cond_value_id = context.node_stack().PopExpression();
cond_value_id = context.ConvertToBoolValue(parse_node, cond_value_id);
auto bool_type_id = context.semantics_ir().GetNode(cond_value_id).type_id();
// Compute the branch value: the condition for `and`, inverted for `or`.
auto token = context.parse_tree().node_token(parse_node);
SemIR::NodeId branch_value_id = SemIR::NodeId::Invalid;
auto short_circuit_result_id = SemIR::NodeId::Invalid;
switch (auto token_kind = context.tokens().GetKind(token)) {
case TokenKind::And:
branch_value_id = cond_value_id;
short_circuit_result_id = context.AddNode(SemIR::Node::BoolLiteral::Make(
parse_node, bool_type_id, SemIR::BoolValue::False));
break;
case TokenKind::Or:
branch_value_id = context.AddNode(SemIR::Node::UnaryOperatorNot::Make(
parse_node, bool_type_id, cond_value_id));
short_circuit_result_id = context.AddNode(SemIR::Node::BoolLiteral::Make(
parse_node, bool_type_id, SemIR::BoolValue::True));
break;
default:
CARBON_FATAL() << "Unexpected short-circuiting operator " << token_kind;
}
// Create a block for the right-hand side and for the continuation.
auto rhs_block_id =
context.AddDominatedBlockAndBranchIf(parse_node, branch_value_id);
auto end_block_id = context.AddDominatedBlockAndBranchWithArg(
parse_node, short_circuit_result_id);
// Push the resumption and the right-hand side blocks, and start emitting the
// right-hand operand.
context.node_block_stack().Pop();
context.node_block_stack().Push(end_block_id);
context.node_block_stack().Push(rhs_block_id);
context.AddCurrentCodeBlockToFunction();
// Put the condition back on the stack for HandleInfixOperator.
context.node_stack().Push(parse_node, cond_value_id);
return true;
}
} // namespace Carbon::Check