Add semantics for struct type and value literals. (#2709)

This handles the basics of type and value for structs. Structurally, these look like parameters and arguments (respectively) because expressions/generics may result in multiple IR nodes being generated.

Because `{}` needs to be cast to a type for storage, I'm also adding some validation that's not specific to `{}`, e.g. that `1` shouldn't be valid as a type for storage (previously, nothing errored for that).

This adds more stringification of types, particularly literals, because they come up in value errors now.

ImplicitAs is the result of me mulling whether I'm taking the right approach on type conversions. I think it needs to return a value so that if the implicit cast rewrites the value, the result is accessible to the caller. I may reorient the current TryTypeConversion logic to be more based on the ImplicitAs logic.
This commit is contained in:
Jon Ross-Perkins
2023-03-30 10:15:11 -07:00
committed by GitHub
parent 33f26bd37b
commit 2eef8c751b
32 changed files with 1200 additions and 206 deletions
@@ -232,68 +232,123 @@ auto SemanticsParseTreeHandler::TryTypeConversionOnArgs(
return true;
}
auto SemanticsParseTreeHandler::ImplicitAs(ParseTree::Node parse_node,
SemanticsNodeId value_id,
SemanticsNodeId as_type_id)
-> SemanticsNodeId {
// Start by making sure both sides are valid. If any part is invalid, the
// result is invalid and we shouldn't error.
if (value_id == SemanticsNodeId::BuiltinInvalidType ||
as_type_id == SemanticsNodeId::BuiltinInvalidType) {
return SemanticsNodeId::BuiltinInvalidType;
}
auto value_type_id = semantics_->GetType(value_id);
if (value_type_id == SemanticsNodeId::BuiltinInvalidType) {
return SemanticsNodeId::BuiltinInvalidType;
}
// If the type doesn't need to change, we can return the value directly.
if (value_type_id == as_type_id) {
return value_id;
}
// When converting to a Type, there are some automatic conversions that can be
// done.
if (as_type_id == SemanticsNodeId::BuiltinTypeType) {
if (value_id == SemanticsNodeId::BuiltinEmptyTuple) {
return SemanticsNodeId::BuiltinEmptyTupleType;
}
if (value_id == SemanticsNodeId::BuiltinEmptyStruct) {
return SemanticsNodeId::BuiltinEmptyStructType;
}
}
auto value_type = semantics_->GetNode(value_type_id);
auto as_type = semantics_->GetNode(as_type_id);
if (CanImplicitAsStruct(value_type, as_type)) {
return value_id;
}
CARBON_DIAGNOSTIC(ImplicitAsConversionFailure, Error,
"Cannot implicitly convert from {0} to {1}.", std::string,
std::string);
emitter_
->Build(parse_node, ImplicitAsConversionFailure,
semantics_->StringifyNode(value_type_id),
semantics_->StringifyNode(as_type_id))
.Emit();
return SemanticsNodeId::BuiltinInvalidType;
}
auto SemanticsParseTreeHandler::CanImplicitAsStruct(SemanticsNode value_type,
SemanticsNode as_type)
-> bool {
if (value_type.kind() != SemanticsNodeKind::StructType ||
as_type.kind() != SemanticsNodeKind::StructType) {
return false;
}
auto value_type_refs =
semantics_->GetNodeBlock(value_type.GetAsStructType().second);
auto as_type_refs =
semantics_->GetNodeBlock(as_type.GetAsStructType().second);
if (value_type_refs.size() != as_type_refs.size()) {
return false;
}
for (int i = 0; i < static_cast<int>(value_type_refs.size()); ++i) {
auto value_type_field = semantics_->GetNode(value_type_refs[i]);
auto as_type_field = semantics_->GetNode(as_type_refs[i]);
if (value_type_field.type_id() != as_type_field.type_id() ||
value_type_field.GetAsStructTypeField() !=
as_type_field.GetAsStructTypeField()) {
return false;
}
}
return true;
}
auto SemanticsParseTreeHandler::ParamOrArgStart() -> void {
params_or_args_stack_.Push();
node_block_stack_.Push();
}
auto SemanticsParseTreeHandler::ParamOrArgComma(ParseTree::Node parse_node)
-> bool {
node_stack_.Push(parse_node);
// Copy the last node added to the IR block into the params block.
if (!ParamOrArgSave()) {
emitter_->Emit(
parse_node, SemanticsTodo,
"Should have a param before comma, will need error recovery");
return false;
}
return true;
auto SemanticsParseTreeHandler::ParamOrArgComma(bool for_args) -> void {
ParamOrArgSave(for_args);
}
auto SemanticsParseTreeHandler::ParamOrArgEnd(
ParseNodeKind start_kind, ParseNodeKind comma_kind,
std::function<bool(SemanticsNodeBlockId, SemanticsNodeBlockId)> on_start)
-> bool {
// If there's a node in the IR block that has yet to be added to the params
// block, add it now.
ParamOrArgSave();
while (true) {
auto parse_kind = parse_tree_->node_kind(node_stack_.PeekParseNode());
if (parse_kind == start_kind) {
return on_start(node_block_stack_.Pop(), params_or_args_stack_.Pop());
} else if (parse_kind == comma_kind) {
node_stack_.PopAndDiscardSoloParseNode(comma_kind);
} else {
node_stack_.PopAndIgnore();
}
auto SemanticsParseTreeHandler::ParamOrArgEnd(bool for_args,
ParseNodeKind start_kind)
-> std::pair<SemanticsNodeBlockId, SemanticsNodeBlockId> {
if (parse_tree_->node_kind(node_stack_.PeekParseNode()) != start_kind) {
ParamOrArgSave(for_args);
}
return {node_block_stack_.Pop(), params_or_args_stack_.Pop()};
}
auto SemanticsParseTreeHandler::ParamOrArgSave() -> bool {
// Copy the last node added to the IR block into the params block.
auto ir_id = node_block_stack_.Peek();
if (!ir_id.is_valid()) {
return false;
auto SemanticsParseTreeHandler::ParamOrArgSave(bool for_args) -> void {
SemanticsNodeId param_or_arg_id = SemanticsNodeId::Invalid;
if (for_args) {
// For an argument, we add a stub reference to the expression on the top of
// the stack. There may not be anything on the IR prior to this.
auto [entry_parse_node, entry_node_id] =
node_stack_.PopForParseNodeAndNodeId();
param_or_arg_id = AddNode(SemanticsNode::StubReference::Make(
entry_parse_node, semantics_->GetNode(entry_node_id).type_id(),
entry_node_id));
} else {
// For a parameter, there should always be something in the IR.
node_stack_.PopAndIgnore();
auto ir_id = node_block_stack_.Peek();
CARBON_CHECK(ir_id.is_valid());
auto& ir = semantics_->GetNodeBlock(ir_id);
CARBON_CHECK(!ir.empty()) << "Should have had a param";
param_or_arg_id = ir.back();
}
// We get params before ir because it may add a node block, which can
// invalidate the ir reference.
auto& params = semantics_->GetNodeBlock(params_or_args_stack_.PeekForAdd());
auto& ir = semantics_->GetNodeBlock(ir_id);
CARBON_CHECK(!ir.empty())
<< "Should only have a valid ID if a node was added";
auto& param = ir.back();
if (!params.empty() && param == params.back()) {
// The param was already added after a comma.
return false;
}
params.push_back(ir.back());
return true;
// Save the param or arg ID.
auto& params_or_args =
semantics_->GetNodeBlock(params_or_args_stack_.PeekForAdd());
params_or_args.push_back(param_or_arg_id);
}
auto SemanticsParseTreeHandler::HandleAddress(ParseTree::Node parse_node)
@@ -316,45 +371,44 @@ auto SemanticsParseTreeHandler::HandleBreakStatementStart(
auto SemanticsParseTreeHandler::HandleCallExpression(ParseTree::Node parse_node)
-> bool {
auto on_start = [&](SemanticsNodeBlockId ir_id,
SemanticsNodeBlockId refs_id) -> bool {
// TODO: Convert to call expression.
auto [call_expr_parse_node, name_id] = node_stack_.PopForParseNodeAndNodeId(
ParseNodeKind::CallExpressionStart);
auto name_node = semantics_->GetNode(name_id);
if (name_node.kind() != SemanticsNodeKind::FunctionDeclaration) {
// TODO: Work on error.
emitter_->Emit(parse_node, SemanticsTodo, "Not a callable name");
node_stack_.Push(parse_node, name_id);
return true;
}
auto [ir_id, refs_id] =
ParamOrArgEnd(/*for_args=*/true, ParseNodeKind::CallExpressionStart);
auto [_, callable_id] = name_node.GetAsFunctionDeclaration();
auto callable = semantics_->GetCallable(callable_id);
if (!TryTypeConversionOnArgs(call_expr_parse_node, ir_id, refs_id,
name_node.parse_node(),
callable.param_refs_id)) {
node_stack_.Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
return true;
}
auto call_id = semantics_->AddCall({ir_id, refs_id});
// TODO: Propagate return types from callable.
auto call_node_id = AddNode(SemanticsNode::Call::Make(
call_expr_parse_node, SemanticsNodeId::BuiltinEmptyTuple, call_id,
callable_id));
node_stack_.Push(parse_node, call_node_id);
// TODO: Convert to call expression.
auto [call_expr_parse_node, name_id] =
node_stack_.PopForParseNodeAndNodeId(ParseNodeKind::CallExpressionStart);
auto name_node = semantics_->GetNode(name_id);
if (name_node.kind() != SemanticsNodeKind::FunctionDeclaration) {
// TODO: Work on error.
emitter_->Emit(parse_node, SemanticsTodo, "Not a callable name");
node_stack_.Push(parse_node, name_id);
return true;
};
return ParamOrArgEnd(ParseNodeKind::CallExpressionStart,
ParseNodeKind::CallExpressionComma, on_start);
}
auto [_, callable_id] = name_node.GetAsFunctionDeclaration();
auto callable = semantics_->GetCallable(callable_id);
if (!TryTypeConversionOnArgs(call_expr_parse_node, ir_id, refs_id,
name_node.parse_node(),
callable.param_refs_id)) {
node_stack_.Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
return true;
}
auto call_id = semantics_->AddCall({ir_id, refs_id});
// TODO: Propagate return types from callable.
auto call_node_id = AddNode(SemanticsNode::Call::Make(
call_expr_parse_node, SemanticsNodeId::BuiltinEmptyTuple, call_id,
callable_id));
node_stack_.Push(parse_node, call_node_id);
return true;
}
auto SemanticsParseTreeHandler::HandleCallExpressionComma(
ParseTree::Node parse_node) -> bool {
return ParamOrArgComma(parse_node);
ParseTree::Node /*parse_node*/) -> bool {
ParamOrArgComma(/*for_args=*/true);
return true;
}
auto SemanticsParseTreeHandler::HandleCallExpressionStart(
@@ -434,8 +488,11 @@ auto SemanticsParseTreeHandler::HandleDeducedParameterListStart(
auto SemanticsParseTreeHandler::HandleDesignatedName(ParseTree::Node parse_node)
-> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleDesignatedName");
return false;
auto name_str = parse_tree_->GetNodeText(parse_node);
auto name_id = semantics_->AddString(name_str);
// The parent is responsible for binding the name.
node_stack_.Push(parse_node, name_id);
return true;
}
auto SemanticsParseTreeHandler::HandleDesignatorExpression(
@@ -782,21 +839,20 @@ auto SemanticsParseTreeHandler::HandlePackageLibrary(ParseTree::Node parse_node)
auto SemanticsParseTreeHandler::HandleParameterList(ParseTree::Node parse_node)
-> bool {
auto on_start = [&](SemanticsNodeBlockId ir_id,
SemanticsNodeBlockId refs_id) -> bool {
PopScope();
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterListStart);
finished_params_stack_.push_back({ir_id, refs_id});
node_stack_.Push(parse_node);
return true;
};
return ParamOrArgEnd(ParseNodeKind::ParameterListStart,
ParseNodeKind::ParameterListComma, on_start);
auto [ir_id, refs_id] =
ParamOrArgEnd(/*for_args=*/false, ParseNodeKind::ParameterListStart);
PopScope();
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ParameterListStart);
finished_params_stack_.push_back({ir_id, refs_id});
node_stack_.Push(parse_node);
return true;
}
auto SemanticsParseTreeHandler::HandleParameterListComma(
ParseTree::Node parse_node) -> bool {
return ParamOrArgComma(parse_node);
ParseTree::Node /*parse_node*/) -> bool {
ParamOrArgComma(/*for_args=*/false);
return true;
}
auto SemanticsParseTreeHandler::HandleParameterListStart(
@@ -822,16 +878,19 @@ auto SemanticsParseTreeHandler::HandleParenExpressionOrTupleLiteralStart(
auto SemanticsParseTreeHandler::HandlePatternBinding(ParseTree::Node parse_node)
-> bool {
auto type = node_stack_.PopForNodeId();
auto [type_node, parsed_type] = node_stack_.PopForParseNodeAndNodeId();
auto cast_type_id =
ImplicitAs(type_node, parsed_type, SemanticsNodeId::BuiltinTypeType);
// Get the name.
auto name_node = node_stack_.PopForSoloParseNode();
// Allocate storage, linked to the name for error locations.
auto storage_id = AddNode(SemanticsNode::VarStorage::Make(name_node, type));
auto storage_id =
AddNode(SemanticsNode::VarStorage::Make(name_node, cast_type_id));
// Bind the name to storage.
auto name_id = BindName(name_node, type, storage_id);
auto name_id = BindName(name_node, cast_type_id, storage_id);
// If this node's result is used, it'll be for either the name or the storage
// address. The storage address can be found through the name, so we push the
@@ -939,22 +998,35 @@ auto SemanticsParseTreeHandler::HandleSelfValueIdentifier(
return false;
}
auto SemanticsParseTreeHandler::HandleStructComma(ParseTree::Node parse_node)
-> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleStructComma");
return false;
auto SemanticsParseTreeHandler::HandleStructComma(
ParseTree::Node /*parse_node*/) -> bool {
ParamOrArgComma(
/*for_args=*/parse_tree_->node_kind(node_stack_.PeekParseNode()) !=
ParseNodeKind::StructFieldType);
return true;
}
auto SemanticsParseTreeHandler::HandleStructFieldDesignator(
ParseTree::Node parse_node) -> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleStructFieldDesignator");
return false;
ParseTree::Node /*parse_node*/) -> bool {
// This leaves the designated name on top because the `.` isn't interesting.
CARBON_CHECK(parse_tree_->node_kind(node_stack_.PeekParseNode()) ==
ParseNodeKind::DesignatedName);
return true;
}
auto SemanticsParseTreeHandler::HandleStructFieldType(
ParseTree::Node parse_node) -> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleStructFieldType");
return false;
auto [type_node, type_id] = node_stack_.PopForParseNodeAndNodeId();
auto cast_type_id =
ImplicitAs(type_node, type_id, SemanticsNodeId::BuiltinTypeType);
auto [name_node, name_id] =
node_stack_.PopForParseNodeAndNameId(ParseNodeKind::DesignatedName);
AddNode(
SemanticsNode::StructTypeField::Make(name_node, cast_type_id, name_id));
node_stack_.Push(parse_node);
return true;
}
auto SemanticsParseTreeHandler::HandleStructFieldUnknown(
@@ -965,27 +1037,81 @@ auto SemanticsParseTreeHandler::HandleStructFieldUnknown(
auto SemanticsParseTreeHandler::HandleStructFieldValue(
ParseTree::Node parse_node) -> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleStructFieldValue");
return false;
auto [value_parse_node, value_node_id] =
node_stack_.PopForParseNodeAndNodeId();
auto [_, name_id] =
node_stack_.PopForParseNodeAndNameId(ParseNodeKind::DesignatedName);
// Store the name for the type.
auto type_block_id = args_type_info_stack_.PeekForAdd();
semantics_->AddNode(
type_block_id,
SemanticsNode::StructTypeField::Make(
parse_node, semantics_->GetNode(value_node_id).type_id(), name_id));
// Push the value back on the stack as an argument.
node_stack_.Push(parse_node, value_node_id);
return true;
}
auto SemanticsParseTreeHandler::HandleStructLiteral(ParseTree::Node parse_node)
-> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleStructLiteral");
return false;
auto [ir_id, refs_id] = ParamOrArgEnd(
/*for_args=*/true, ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
PopScope();
node_stack_.PopAndDiscardSoloParseNode(
ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
auto type_block_id = args_type_info_stack_.Pop();
// Special-case `{}`.
if (refs_id == SemanticsNodeBlockId::Empty) {
node_stack_.Push(parse_node, SemanticsNodeId::BuiltinEmptyStruct);
return true;
}
// Construct a type for the literal. Each field is one node, so ir_id and
// refs_id match.
auto refs = semantics_->GetNodeBlock(refs_id);
auto type_id = AddNode(SemanticsNode::StructType::Make(
parse_node, type_block_id, type_block_id));
auto value_id = AddNode(
SemanticsNode::StructValue::Make(parse_node, type_id, ir_id, refs_id));
node_stack_.Push(parse_node, value_id);
return true;
}
auto SemanticsParseTreeHandler::HandleStructLiteralOrStructTypeLiteralStart(
ParseTree::Node parse_node) -> bool {
emitter_->Emit(parse_node, SemanticsTodo,
"HandleStructLiteralOrStructTypeLiteralStart");
return false;
PushScope();
node_stack_.Push(parse_node);
// At this point we aren't sure whether this will be a value or type literal,
// so we push onto args irrespective. It just won't be used for a type
// literal.
args_type_info_stack_.Push();
ParamOrArgStart();
return true;
}
auto SemanticsParseTreeHandler::HandleStructTypeLiteral(
ParseTree::Node parse_node) -> bool {
emitter_->Emit(parse_node, SemanticsTodo, "HandleStructTypeLiteral");
return false;
auto [ir_id, refs_id] = ParamOrArgEnd(
/*for_args=*/false, ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
PopScope();
node_stack_.PopAndDiscardSoloParseNode(
ParseNodeKind::StructLiteralOrStructTypeLiteralStart);
// This is only used for value literals.
args_type_info_stack_.Pop();
CARBON_CHECK(refs_id != SemanticsNodeBlockId::Empty)
<< "{} is handled by StructLiteral.";
auto type_id =
AddNode(SemanticsNode::StructType::Make(parse_node, ir_id, refs_id));
node_stack_.Push(parse_node, type_id);
return true;
}
auto SemanticsParseTreeHandler::HandleTemplate(ParseTree::Node parse_node)
@@ -1015,16 +1141,17 @@ auto SemanticsParseTreeHandler::HandleVariableDeclaration(
auto storage_id =
node_stack_.PopForNodeId(ParseNodeKind::VariableInitializer);
auto binding = node_stack_.PopForParseNodeAndNameId();
auto binding =
node_stack_.PopForParseNodeAndNameId(ParseNodeKind::PatternBinding);
// Restore the name now that the initializer is complete.
ReaddNameToLookup(binding.second, storage_id);
auto storage_type =
TryTypeConversion(parse_node, storage_id, last_child.second,
/*can_convert_lhs=*/false);
AddNode(SemanticsNode::Assign::Make(parse_node, storage_type, storage_id,
last_child.second));
auto cast_value_id = ImplicitAs(parse_node, last_child.second,
semantics_->GetType(storage_id));
AddNode(SemanticsNode::Assign::Make(parse_node,
semantics_->GetType(cast_value_id),
storage_id, cast_value_id));
}
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::VariableIntroducer);
@@ -1046,7 +1173,8 @@ auto SemanticsParseTreeHandler::HandleVariableInitializer(
// restored by `VariableDeclaration`.
// Save the storage ID.
auto it = name_lookup_.find(node_stack_.PeekForNameId());
auto it = name_lookup_.find(
node_stack_.PeekForNameId(ParseNodeKind::PatternBinding));
CARBON_CHECK(it != name_lookup_.end());
CARBON_CHECK(!it->second.empty());
auto storage_id = it->second.back();