mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 07:41:04 +01:00
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user