Consolidate towards ImplicitAs handling. (#2719)

This eliminates the older TryTypeConversion approach in favor of the ImplicitAs logic. This makes `return {}` work correctly.
This commit is contained in:
Jon Ross-Perkins
2023-03-31 08:11:17 -07:00
committed by GitHub
parent 1f741c292f
commit 7824e9618c
28 changed files with 403 additions and 194 deletions
@@ -139,48 +139,10 @@ auto SemanticsParseTreeHandler::PopScope() -> void {
}
}
auto SemanticsParseTreeHandler::CanTypeConvert(SemanticsNodeId from_type,
SemanticsNodeId to_type)
-> SemanticsNodeId {
// TODO: This should attempt implicit conversions, but there's not enough
// implemented to do that right now.
if (from_type == SemanticsNodeId::BuiltinInvalidType ||
to_type == SemanticsNodeId::BuiltinInvalidType) {
return SemanticsNodeId::BuiltinInvalidType;
}
if (from_type == to_type) {
return from_type;
}
return SemanticsNodeId::Invalid;
}
auto SemanticsParseTreeHandler::TryTypeConversion(ParseTree::Node parse_node,
SemanticsNodeId lhs_id,
SemanticsNodeId rhs_id,
bool /*can_convert_lhs*/)
-> SemanticsNodeId {
auto lhs_type = semantics_->GetType(lhs_id);
auto rhs_type = semantics_->GetType(rhs_id);
// TODO: CanTypeConvert can be assumed to handle rhs conversions, and we'll
// either want to call it twice or refactor it to be aware of lhs conversions.
auto type = CanTypeConvert(rhs_type, lhs_type);
if (type.is_valid()) {
return type;
}
CARBON_DIAGNOSTIC(TypeMismatch, Error,
"Type mismatch: lhs is {0}, rhs is {1}", std::string,
std::string);
emitter_->Emit(parse_node, TypeMismatch, semantics_->StringifyNode(lhs_type),
semantics_->StringifyNode(rhs_type));
return SemanticsNodeId::BuiltinInvalidType;
}
auto SemanticsParseTreeHandler::TryTypeConversionOnArgs(
ParseTree::Node arg_parse_node, SemanticsNodeBlockId /*arg_ir_id*/,
SemanticsNodeBlockId arg_refs_id, ParseTree::Node param_parse_node,
SemanticsNodeBlockId param_refs_id) -> bool {
CARBON_DIAGNOSTIC(NoMatchingCall, Error, "No matching callable was found.");
auto SemanticsParseTreeHandler::ImplicitAsForArgs(
SemanticsNodeBlockId /*arg_ir_id*/, SemanticsNodeBlockId arg_refs_id,
ParseTree::Node param_parse_node, SemanticsNodeBlockId param_refs_id,
DiagnosticEmitter<ParseTree::Node>::DiagnosticBuilder* diagnostic) -> bool {
// If both arguments and parameters are empty, return quickly. Otherwise,
// we'll fetch both so that errors are consistent.
if (arg_refs_id == SemanticsNodeBlockId::Empty &&
@@ -193,13 +155,13 @@ auto SemanticsParseTreeHandler::TryTypeConversionOnArgs(
// If sizes mismatch, fail early.
if (arg_refs.size() != param_refs.size()) {
CARBON_CHECK(diagnostic != nullptr) << "Should have validated first";
CARBON_DIAGNOSTIC(CallArgCountMismatch, Note,
"Received {0} argument(s), but require {1} argument(s).",
"Callable cannot be used: Received {0} argument(s), but "
"require {1} argument(s).",
int, int);
emitter_->Build(arg_parse_node, NoMatchingCall)
.Note(param_parse_node, CallArgCountMismatch, arg_refs.size(),
param_refs.size())
.Emit();
diagnostic->Note(param_parse_node, CallArgCountMismatch, arg_refs.size(),
param_refs.size());
return false;
}
@@ -207,22 +169,20 @@ auto SemanticsParseTreeHandler::TryTypeConversionOnArgs(
// TODO: arg_ir_id is passed so that implicit conversions can be inserted.
// It's currently not supported, but will be needed.
for (size_t i = 0; i < arg_refs.size(); ++i) {
const auto& arg_ref = arg_refs[i];
auto arg_ref_type = semantics_->GetType(arg_ref);
const auto& param_ref = param_refs[i];
auto param_ref_type = semantics_->GetType(param_ref);
auto result_type = CanTypeConvert(arg_ref_type, param_ref_type);
if (!result_type.is_valid()) {
CARBON_DIAGNOSTIC(
CallArgTypeMismatch, Note,
"Type mismatch: cannot convert argument {0} from {1} to {2}.", size_t,
std::string, std::string);
emitter_->Build(arg_parse_node, NoMatchingCall)
.Note(param_parse_node, CallArgTypeMismatch, i,
semantics_->StringifyNode(arg_ref_type),
semantics_->StringifyNode(param_ref_type))
.Emit();
auto value_id = arg_refs[i];
auto as_type_id = semantics_->GetNode(param_refs[i]).type_id();
if (ImplicitAsImpl(value_id, as_type_id,
diagnostic == nullptr ? &value_id : nullptr) ==
ImplicitAsKind::Incompatible) {
CARBON_CHECK(diagnostic != nullptr) << "Should have validated first";
CARBON_DIAGNOSTIC(CallArgTypeMismatch, Note,
"Callable cannot be used: Cannot implicityly convert "
"argument {0} from `{1}` to `{2}`.",
size_t, std::string, std::string);
diagnostic->Note(
param_parse_node, CallArgTypeMismatch, i,
semantics_->StringifyNode(semantics_->GetNode(value_id).type_id()),
semantics_->StringifyNode(as_type_id));
return false;
}
}
@@ -230,52 +190,86 @@ auto SemanticsParseTreeHandler::TryTypeConversionOnArgs(
return true;
}
auto SemanticsParseTreeHandler::ImplicitAs(ParseTree::Node parse_node,
SemanticsNodeId value_id,
SemanticsNodeId as_type_id)
auto SemanticsParseTreeHandler::ImplicitAsRequired(ParseTree::Node parse_node,
SemanticsNodeId value_id,
SemanticsNodeId as_type_id)
-> SemanticsNodeId {
SemanticsNodeId output_value_id = value_id;
if (ImplicitAsImpl(value_id, as_type_id, &output_value_id) ==
ImplicitAsKind::Incompatible) {
// Only error when the system is trying to use the result.
CARBON_DIAGNOSTIC(ImplicitAsConversionFailure, Error,
"Cannot implicitly convert from `{0}` to `{1}`.",
std::string, std::string);
emitter_
->Build(
parse_node, ImplicitAsConversionFailure,
semantics_->StringifyNode(semantics_->GetNode(value_id).type_id()),
semantics_->StringifyNode(as_type_id))
.Emit();
}
return output_value_id;
}
auto SemanticsParseTreeHandler::ImplicitAsImpl(SemanticsNodeId value_id,
SemanticsNodeId as_type_id,
SemanticsNodeId* output_value_id)
-> ImplicitAsKind {
// 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;
if (value_id == SemanticsNodeId::BuiltinInvalidType) {
// If the value is invalid, we can't do much, but do "succeed".
return ImplicitAsKind::Identical;
}
auto value_type_id = semantics_->GetType(value_id);
auto value_type_id = semantics_->GetNode(value_id).type_id();
if (value_type_id == SemanticsNodeId::BuiltinInvalidType) {
return SemanticsNodeId::BuiltinInvalidType;
return ImplicitAsKind::Identical;
}
if (as_type_id == SemanticsNodeId::BuiltinInvalidType) {
// Although the target type is invalid, this still changes the value.
if (output_value_id != nullptr) {
*output_value_id = SemanticsNodeId::BuiltinInvalidType;
}
return ImplicitAsKind::Compatible;
}
// If the type doesn't need to change, we can return the value directly.
if (value_type_id == as_type_id) {
return value_id;
// Type doesn't need to change.
return ImplicitAsKind::Identical;
}
// 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 (output_value_id != nullptr) {
*output_value_id = SemanticsNodeId::BuiltinEmptyTupleType;
}
return ImplicitAsKind::Compatible;
}
if (value_id == SemanticsNodeId::BuiltinEmptyStruct) {
return SemanticsNodeId::BuiltinEmptyStructType;
if (output_value_id != nullptr) {
*output_value_id = SemanticsNodeId::BuiltinEmptyStructType;
}
return ImplicitAsKind::Compatible;
}
}
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;
// Under the current implementation, struct types are only allowed to
// ImplicitAs when they're equivalent. What's really missing is type
// consolidation such that this would fall under the above `value_type_id ==
// as_type_id` case. In the future, this will need to handle actual
// conversions.
return ImplicitAsKind::Identical;
}
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;
if (output_value_id != nullptr) {
*output_value_id = SemanticsNodeId::BuiltinInvalidType;
}
return ImplicitAsKind::Incompatible;
}
auto SemanticsParseTreeHandler::CanImplicitAsStruct(SemanticsNode value_type,
@@ -386,18 +380,23 @@ auto SemanticsParseTreeHandler::HandleCallExpression(ParseTree::Node parse_node)
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)) {
CARBON_DIAGNOSTIC(NoMatchingCall, Error, "No matching callable was found.");
auto diagnostic = emitter_->Build(call_expr_parse_node, NoMatchingCall);
if (!ImplicitAsForArgs(ir_id, refs_id, name_node.parse_node(),
callable.param_refs_id, &diagnostic)) {
diagnostic.Emit();
node_stack_.Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
return true;
}
CARBON_CHECK(ImplicitAsForArgs(ir_id, refs_id, name_node.parse_node(),
callable.param_refs_id,
/*diagnostic=*/nullptr));
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));
call_expr_parse_node, callable.return_type_id, call_id, callable_id));
node_stack_.Push(parse_node, call_node_id);
return true;
@@ -685,15 +684,20 @@ auto SemanticsParseTreeHandler::HandleInfixOperator(ParseTree::Node parse_node)
-> bool {
auto rhs_id = node_stack_.PopForNodeId();
auto lhs_id = node_stack_.PopForNodeId();
SemanticsNodeId result_type =
TryTypeConversion(parse_node, lhs_id, rhs_id, /*can_convert_lhs=*/true);
// TODO: This should search for a compatible interface. For now, it's a very
// trivial check of validity on the operation.
lhs_id = ImplicitAsRequired(parse_node, lhs_id,
semantics_->GetNode(rhs_id).type_id());
// Figure out the operator for the token.
auto token = parse_tree_->node_token(parse_node);
switch (auto token_kind = tokens_->GetKind(token)) {
case TokenKind::Plus:
AddNodeAndPush(parse_node, SemanticsNode::BinaryOperatorAdd::Make(
parse_node, result_type, lhs_id, rhs_id));
AddNodeAndPush(parse_node,
SemanticsNode::BinaryOperatorAdd::Make(
parse_node, semantics_->GetNode(lhs_id).type_id(),
lhs_id, rhs_id));
break;
default:
emitter_->Emit(parse_node, SemanticsTodo,
@@ -915,9 +919,9 @@ auto SemanticsParseTreeHandler::HandleParenExpressionOrTupleLiteralStart(
auto SemanticsParseTreeHandler::HandlePatternBinding(ParseTree::Node parse_node)
-> bool {
auto [type_node, parsed_type] = node_stack_.PopForParseNodeAndNodeId();
auto cast_type_id =
ImplicitAs(type_node, parsed_type, SemanticsNodeId::BuiltinTypeType);
auto [type_node, parsed_type_id] = node_stack_.PopForParseNodeAndNodeId();
SemanticsNodeId cast_type_id = ImplicitAsRequired(
type_node, parsed_type_id, SemanticsNodeId::BuiltinTypeType);
// Get the name.
auto name_node = node_stack_.PopForSoloParseNode();
@@ -929,9 +933,9 @@ auto SemanticsParseTreeHandler::HandlePatternBinding(ParseTree::Node parse_node)
// Bind the name to storage.
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
// name.
// 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 name.
node_stack_.Push(parse_node, name_id);
return true;
@@ -972,8 +976,7 @@ auto SemanticsParseTreeHandler::HandleReturnStatement(
AddNodeAndPush(parse_node, SemanticsNode::Return::Make(parse_node));
} else {
const auto arg = node_stack_.PopForNodeId();
auto arg_type = semantics_->GetType(arg);
auto arg = node_stack_.PopForNodeId();
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::ReturnStatementStart);
if (!callable.return_type_id.is_valid()) {
@@ -986,23 +989,12 @@ auto SemanticsParseTreeHandler::HandleReturnStatement(
.Note(fn_node.parse_node(), ReturnStatementImplicitNote)
.Emit();
} else {
const auto new_type = CanTypeConvert(arg_type, callable.return_type_id);
if (!new_type.is_valid()) {
// TODO: Add a note pointing at the return type's parse node.
CARBON_DIAGNOSTIC(ReturnStatementTypeMismatch, Error,
"Cannot convert {0} to {1}.", std::string,
std::string);
emitter_
->Build(parse_node, ReturnStatementTypeMismatch,
semantics_->StringifyNode(arg_type),
semantics_->StringifyNode(callable.return_type_id))
.Emit();
}
arg_type = new_type;
arg = ImplicitAsRequired(parse_node, arg, callable.return_type_id);
}
AddNodeAndPush(parse_node, SemanticsNode::ReturnExpression::Make(
parse_node, arg_type, arg));
AddNodeAndPush(parse_node,
SemanticsNode::ReturnExpression::Make(
parse_node, semantics_->GetNode(arg).type_id(), arg));
}
return true;
}
@@ -1017,7 +1009,10 @@ auto SemanticsParseTreeHandler::HandleReturnStatementStart(
auto SemanticsParseTreeHandler::HandleReturnType(ParseTree::Node parse_node)
-> bool {
// Propagate the type expression.
node_stack_.Push(parse_node, node_stack_.PopForNodeId());
auto [type_parse_node, type_node_id] = node_stack_.PopForParseNodeAndNodeId();
auto cast_node_id = ImplicitAsRequired(type_parse_node, type_node_id,
SemanticsNodeId::BuiltinTypeType);
node_stack_.Push(parse_node, cast_node_id);
return true;
}
@@ -1052,8 +1047,8 @@ auto SemanticsParseTreeHandler::HandleStructFieldDesignator(
auto SemanticsParseTreeHandler::HandleStructFieldType(
ParseTree::Node parse_node) -> bool {
auto [type_node, type_id] = node_stack_.PopForParseNodeAndNodeId();
auto cast_type_id =
ImplicitAs(type_node, type_id, SemanticsNodeId::BuiltinTypeType);
SemanticsNodeId cast_type_id =
ImplicitAsRequired(type_node, type_id, SemanticsNodeId::BuiltinTypeType);
auto [name_node, name_id] =
node_stack_.PopForParseNodeAndNameId(ParseNodeKind::DesignatedName);
@@ -1169,9 +1164,9 @@ auto SemanticsParseTreeHandler::HandleTupleLiteralComma(
auto SemanticsParseTreeHandler::HandleVariableDeclaration(
ParseTree::Node parse_node) -> bool {
auto last_child = node_stack_.PopForParseNodeAndNodeId();
auto [last_parse_node, last_node_id] = node_stack_.PopForParseNodeAndNodeId();
if (parse_tree_->node_kind(last_child.first) !=
if (parse_tree_->node_kind(last_parse_node) !=
ParseNodeKind::PatternBinding) {
auto storage_id =
node_stack_.PopForNodeId(ParseNodeKind::VariableInitializer);
@@ -1182,11 +1177,11 @@ auto SemanticsParseTreeHandler::HandleVariableDeclaration(
// Restore the name now that the initializer is complete.
ReaddNameToLookup(binding.second, storage_id);
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));
auto cast_value_id = ImplicitAsRequired(
parse_node, last_node_id, semantics_->GetNode(storage_id).type_id());
AddNode(SemanticsNode::Assign::Make(
parse_node, semantics_->GetNode(cast_value_id).type_id(), storage_id,
cast_value_id));
}
node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::VariableIntroducer);