Support user-defined implicit conversions via ImplicitAs (#1273)

Support is added for all of the non-type expression contexts where we currently accept built-in conversions, such as reordering the fields in a struct.
This commit is contained in:
Richard Smith
2022-05-20 17:10:30 -07:00
committed by GitHub
parent e77fcded86
commit 8dd398807d
33 changed files with 999 additions and 97 deletions
+1
View File
@@ -54,6 +54,7 @@ abstract class Expression : AstNode;
class StructLiteral : Expression;
class StructTypeLiteral : Expression;
class TypeTypeLiteral : Expression;
class ValueLiteral : Expression;
class IdentifierExpression : Expression;
class IntrinsicExpression : Expression;
class IfExpression : Expression;
+6
View File
@@ -295,6 +295,12 @@ class VariableDeclaration : public Declaration {
auto has_initializer() const -> bool { return initializer_.has_value(); }
// Can only be called by type-checking, if a conversion was required.
void set_initializer(Nonnull<Expression*> initializer) {
CARBON_CHECK(has_initializer()) << "should not add a new initializer";
initializer_ = initializer;
}
private:
// TODO: split this into a non-optional name and a type, initialized by
// a constructor that takes a BindingPattern and handles errors like a
+5
View File
@@ -200,6 +200,7 @@ void Expression::Print(llvm::raw_ostream& out) const {
case ExpressionKind::StringTypeLiteral:
case ExpressionKind::TypeTypeLiteral:
case ExpressionKind::ContinuationTypeLiteral:
case ExpressionKind::ValueLiteral:
PrintID(out);
break;
}
@@ -236,6 +237,10 @@ void Expression::PrintID(llvm::raw_ostream& out) const {
case ExpressionKind::ContinuationTypeLiteral:
out << "Continuation";
break;
case ExpressionKind::ValueLiteral:
// FIXME: For layering reasons, we can't print out the value from here.
out << "ValueLiteral";
break;
case ExpressionKind::IndexExpression:
case ExpressionKind::FieldAccessExpression:
case ExpressionKind::CompoundFieldAccessExpression:
+38
View File
@@ -70,6 +70,12 @@ class Expression : public AstNode {
value_category_ = value_category;
}
// Determines whether the expression has already been type-checked. Should
// only be used by type-checking.
auto is_type_checked() -> bool {
return static_type_.has_value() && value_category_.has_value();
}
protected:
// Constructs an Expression representing syntax at the given line number.
// `kind` must be the enumerator corresponding to the most-derived type being
@@ -238,6 +244,9 @@ class CompoundFieldAccessExpression : public Expression {
impl_ = impl;
}
// Can only be called by type-checking, if a conversion was required.
void set_object(Nonnull<Expression*> object) { object_ = object; }
private:
Nonnull<Expression*> object_;
Nonnull<Expression*> path_;
@@ -467,6 +476,9 @@ class CallExpression : public Expression {
deduced_args_ = deduced_args;
}
// Can only be called by type-checking, if a conversion was required.
void set_argument(Nonnull<Expression*> argument) { argument_ = argument; }
private:
Nonnull<Expression*> function_;
Nonnull<Expression*> argument_;
@@ -537,6 +549,29 @@ class TypeTypeLiteral : public Expression {
}
};
// A literal value. This is used in desugaring, and can't be expressed in
// source syntax.
class ValueLiteral : public Expression {
public:
// Value literals are created by type-checking, and so are created with their
// type and value category already known.
ValueLiteral(SourceLocation source_loc, Nonnull<const Value*> value,
Nonnull<const Value*> type, ValueCategory value_category)
: Expression(AstNodeKind::ValueLiteral, source_loc), value_(value) {
set_static_type(type);
set_value_category(value_category);
}
static auto classof(const AstNode* node) -> bool {
return InheritsFromValueLiteral(node->kind());
}
auto value() const -> const Value& { return *value_; }
private:
Nonnull<const Value*> value_;
};
class IntrinsicExpression : public Expression {
public:
enum class Intrinsic {
@@ -595,6 +630,9 @@ class IfExpression : public Expression {
}
auto else_expression() -> Expression& { return *else_expression_; }
// Can only be called by type-checking, if a conversion was required.
void set_condition(Nonnull<Expression*> condition) { condition_ = condition; }
private:
Nonnull<Expression*> condition_;
Nonnull<Expression*> then_expression_;
+25
View File
@@ -101,6 +101,9 @@ class Assign : public Statement {
auto rhs() const -> const Expression& { return *rhs_; }
auto rhs() -> Expression& { return *rhs_; }
// Can only be called by type-checking, if a conversion was required.
void set_rhs(Nonnull<Expression*> rhs) { rhs_ = rhs; }
private:
Nonnull<Expression*> lhs_;
Nonnull<Expression*> rhs_;
@@ -125,6 +128,9 @@ class VariableDefinition : public Statement {
auto init() -> Expression& { return *init_; }
auto value_category() const -> ValueCategory { return value_category_; }
// Can only be called by type-checking, if a conversion was required.
void set_init(Nonnull<Expression*> init) { init_ = init; }
private:
Nonnull<Pattern*> pattern_;
Nonnull<Expression*> init_;
@@ -153,6 +159,9 @@ class If : public Statement {
}
auto else_block() -> std::optional<Nonnull<Block*>> { return else_block_; }
// Can only be called by type-checking, if a conversion was required.
void set_condition(Nonnull<Expression*> condition) { condition_ = condition; }
private:
Nonnull<Expression*> condition_;
Nonnull<Block*> then_block_;
@@ -186,6 +195,11 @@ class Return : public Statement {
auto function() const -> const FunctionDeclaration& { return **function_; }
auto function() -> FunctionDeclaration& { return **function_; }
// Can only be called by type-checking, if a conversion was required.
void set_expression(Nonnull<Expression*> expression) {
expression_ = expression;
}
// Can only be called once, by ResolveControlFlow.
void set_function(Nonnull<FunctionDeclaration*> function) {
CARBON_CHECK(!function_.has_value());
@@ -215,6 +229,9 @@ class While : public Statement {
auto body() const -> const Block& { return *body_; }
auto body() -> Block& { return *body_; }
// Can only be called by type-checking, if a conversion was required.
void set_condition(Nonnull<Expression*> condition) { condition_ = condition; }
private:
Nonnull<Expression*> condition_;
Nonnull<Block*> body_;
@@ -306,6 +323,11 @@ class Match : public Statement {
auto clauses() const -> llvm::ArrayRef<Clause> { return clauses_; }
auto clauses() -> llvm::MutableArrayRef<Clause> { return clauses_; }
// Can only be called by type-checking, if a conversion was required.
void set_expression(Nonnull<Expression*> expression) {
expression_ = expression;
}
private:
Nonnull<Expression*> expression_;
std::vector<Clause> clauses_;
@@ -376,6 +398,9 @@ class Run : public Statement {
auto argument() const -> const Expression& { return *argument_; }
auto argument() -> Expression& { return *argument_; }
// Can only be called by type-checking, if a conversion was required.
void set_argument(Nonnull<Expression*> argument) { argument_ = argument; }
private:
Nonnull<Expression*> argument_;
};
+30
View File
@@ -4,6 +4,36 @@
package Carbon api;
// Implicitly convert `Self` to `T`.
interface ImplicitAs(T:! Type) {
fn Convert[me: Self]() -> T;
}
// TODO: Simplify this once we have variadics.
// TODO: Should these be final?
impl forall [U1:! Type, T1:! ImplicitAs(U1)]
(T1,) as ImplicitAs((U1,)) {
fn Convert[me: Self]() -> (U1,) {
let (v1: T1,) = me;
return (v1.Convert(),);
}
}
impl forall [U1:! Type, U2:! Type, T1:! ImplicitAs(U1), T2:! ImplicitAs(U2)]
(T1, T2) as ImplicitAs((U1, U2)) {
fn Convert[me: Self]() -> (U1, U2) {
let (v1: T1, v2: T2) = me;
return (v1.Convert(), v2.Convert());
}
}
impl forall [U1:! Type, U2:! Type, U3:! Type,
T1:! ImplicitAs(U1), T2:! ImplicitAs(U2), T3:! ImplicitAs(U3)]
(T1, T2, T3) as ImplicitAs((U1, U2, U3)) {
fn Convert[me: Self]() -> (U1, U2, U3) {
let (v1: T1, v2: T2, v3: T3) = me;
return (v1.Convert(), v2.Convert(), v3.Convert());
}
}
// Note that Print is experimental, and not part of an accepted proposal, but
// is included here for printing state in tests.
fn Print(format_str: String) {
+3 -2
View File
@@ -80,8 +80,9 @@ static auto ExpressionToProto(const Expression& expression)
-> Fuzzing::Expression {
Fuzzing::Expression expression_proto;
switch (expression.kind()) {
case ExpressionKind::InstantiateImpl: {
// UNDER CONSTRUCTION
case ExpressionKind::InstantiateImpl:
case ExpressionKind::ValueLiteral: {
// These do not correspond to source syntax.
break;
}
case ExpressionKind::CallExpression: {
+4
View File
@@ -192,10 +192,12 @@ cc_library(
cc_library(
name = "type_checker",
srcs = [
"builtins.cpp",
"impl_scope.cpp",
"type_checker.cpp",
],
hdrs = [
"builtins.h",
"impl_scope.h",
"type_checker.h",
],
@@ -203,6 +205,7 @@ cc_library(
":action_and_value",
":dictionary",
":interpreter",
"//common:error",
"//common:ostream",
"//explorer/ast",
"//explorer/ast:declaration",
@@ -211,6 +214,7 @@ cc_library(
"//explorer/common:arena",
"//explorer/common:error_builders",
"//explorer/common:nonnull",
"//explorer/common:source_location",
"@llvm-project//llvm:Support",
],
)
+32
View File
@@ -0,0 +1,32 @@
// 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 "explorer/interpreter/builtins.h"
#include "explorer/common/error_builders.h"
using llvm::dyn_cast;
namespace Carbon {
void Builtins::Register(Nonnull<const Declaration*> decl) {
if (auto* interface = dyn_cast<InterfaceDeclaration>(decl)) {
if (interface->name() == GetName(Builtin::ImplicitAs)) {
builtins_[static_cast<int>(Builtin::ImplicitAs)] = interface;
}
}
}
auto Builtins::Get(SourceLocation source_loc, Builtin builtin) const
-> ErrorOr<Nonnull<const Declaration*>> {
std::optional<const Declaration*> result =
builtins_[static_cast<int>(builtin)];
if (!result.has_value()) {
return CompilationError(source_loc)
<< "missing declaration for builtin `" << GetName(builtin) << "`";
}
return result.value();
}
} // namespace Carbon
+48
View File
@@ -0,0 +1,48 @@
// 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
#ifndef CARBON_EXPLORER_INTERPRETER_BUILTINS_H_
#define CARBON_EXPLORER_INTERPRETER_BUILTINS_H_
#include <optional>
#include "common/error.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/expression.h"
#include "explorer/common/nonnull.h"
#include "explorer/common/source_location.h"
#include "explorer/interpreter/value.h"
namespace Carbon {
class Builtins {
public:
explicit Builtins() {}
enum class Builtin { ImplicitAs, Last = ImplicitAs };
// FIXME: In C++20, replace with `using enum Builtin;`.
static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;
// Register a declaration that might be a builtin.
void Register(Nonnull<const Declaration*> decl);
// Get a registered builtin.
auto Get(SourceLocation source_loc, Builtin builtin) const
-> ErrorOr<Nonnull<const Declaration*>>;
// Get the source name of a builtin.
static constexpr auto GetName(Builtin builtin) -> const char* {
return BuiltinNames[static_cast<int>(builtin)];
}
private:
static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
static constexpr const char* BuiltinNames[NumBuiltins] = {"ImplicitAs"};
std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
};
} // namespace Carbon
#endif // CARBON_EXPLORER_INTERPRETER_BUILTINS_H_
+5
View File
@@ -401,6 +401,7 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
case ExpressionKind::ContinuationTypeLiteral:
case ExpressionKind::StringLiteral:
case ExpressionKind::StringTypeLiteral:
case ExpressionKind::ValueLiteral:
case ExpressionKind::IntrinsicExpression:
case ExpressionKind::IfExpression:
case ExpressionKind::ArrayTypeLiteral:
@@ -1042,6 +1043,10 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
CARBON_CHECK(act.pos() == 0);
return todo_.FinishAction(arena_->New<StringType>());
}
case ExpressionKind::ValueLiteral: {
CARBON_CHECK(act.pos() == 0);
return todo_.FinishAction(&cast<ValueLiteral>(exp).value());
}
case ExpressionKind::IfExpression: {
const auto& if_expr = cast<IfExpression>(exp);
if (act.pos() == 0) {
+1
View File
@@ -191,6 +191,7 @@ static auto ResolveNames(Expression& expression,
case ExpressionKind::StringLiteral:
case ExpressionKind::StringTypeLiteral:
case ExpressionKind::TypeTypeLiteral:
case ExpressionKind::ValueLiteral:
break;
case ExpressionKind::InstantiateImpl: // created after name resolution
case ExpressionKind::UnimplementedExpression:
+292 -85
View File
@@ -23,6 +23,7 @@
using llvm::cast;
using llvm::dyn_cast;
using llvm::dyn_cast_or_null;
using llvm::isa;
namespace Carbon {
@@ -267,7 +268,12 @@ auto TypeChecker::FieldTypesImplicitlyConvertible(
FindField(destination_fields, source_field.name);
if (!destination_field.has_value() ||
!IsImplicitlyConvertible(source_field.value,
destination_field.value().value)) {
destination_field.value().value,
// FIXME: We don't have a way to perform
// user-defined conversions of a struct field
// yet, because we can't write a suitable impl
// for ImplicitAs.
std::nullopt)) {
return false;
}
}
@@ -295,8 +301,11 @@ auto TypeChecker::FieldTypes(const NominalClassType& class_type) const
}
auto TypeChecker::IsImplicitlyConvertible(
Nonnull<const Value*> source, Nonnull<const Value*> destination) const
-> bool {
Nonnull<const Value*> source, Nonnull<const Value*> destination,
std::optional<Nonnull<const ImplScope*>> impl_scope) const -> bool {
// Check for an exact match or for an implicit conversion.
// FIXME: `impl`s of `ImplicitAs` should be provided to cover these
// conversions.
CARBON_CHECK(IsConcreteType(source));
CARBON_CHECK(IsConcreteType(destination));
if (TypeEqual(source, destination)) {
@@ -306,16 +315,23 @@ auto TypeChecker::IsImplicitlyConvertible(
case Value::Kind::StructType:
switch (destination->kind()) {
case Value::Kind::StructType:
return FieldTypesImplicitlyConvertible(
cast<StructType>(*source).fields(),
cast<StructType>(*destination).fields());
if (FieldTypesImplicitlyConvertible(
cast<StructType>(*source).fields(),
cast<StructType>(*destination).fields())) {
return true;
}
break;
case Value::Kind::NominalClassType:
return FieldTypesImplicitlyConvertible(
cast<StructType>(*source).fields(),
FieldTypes(cast<NominalClassType>(*destination)));
if (FieldTypesImplicitlyConvertible(
cast<StructType>(*source).fields(),
FieldTypes(cast<NominalClassType>(*destination)))) {
return true;
}
break;
default:
return false;
break;
}
break;
case Value::Kind::TupleValue: {
const auto& source_tuple = cast<TupleValue>(*source);
switch (destination->kind()) {
@@ -323,58 +339,188 @@ auto TypeChecker::IsImplicitlyConvertible(
const auto& destination_tuple = cast<TupleValue>(*destination);
if (source_tuple.elements().size() !=
destination_tuple.elements().size()) {
return false;
break;
}
bool all_ok = true;
for (size_t i = 0; i < source_tuple.elements().size(); ++i) {
if (!IsImplicitlyConvertible(source_tuple.elements()[i],
destination_tuple.elements()[i])) {
return false;
destination_tuple.elements()[i],
impl_scope)) {
all_ok = false;
break;
}
}
return true;
if (all_ok) {
return true;
}
break;
}
case Value::Kind::StaticArrayType: {
const auto& destination_array = cast<StaticArrayType>(*destination);
if (destination_array.size() != source_tuple.elements().size()) {
return false;
break;
}
bool all_ok = true;
for (Nonnull<const Value*> source_element : source_tuple.elements()) {
if (!IsImplicitlyConvertible(source_element,
&destination_array.element_type())) {
return false;
&destination_array.element_type(),
impl_scope)) {
all_ok = false;
break;
}
}
return true;
if (all_ok) {
return true;
}
break;
}
case Value::Kind::TypeType: {
bool all_types = true;
for (Nonnull<const Value*> source_element : source_tuple.elements()) {
if (!IsImplicitlyConvertible(source_element, destination)) {
return false;
if (!IsImplicitlyConvertible(source_element, destination,
impl_scope)) {
all_types = false;
break;
}
}
return true;
if (all_types) {
return true;
}
break;
}
default:
return false;
break;
}
break;
}
case Value::Kind::TypeType:
return destination->kind() == Value::Kind::InterfaceType;
// FIXME: This seems suspicious. Shouldn't this require that the type
// implements the interface?
if (destination->kind() == Value::Kind::InterfaceType) {
return true;
}
break;
case Value::Kind::InterfaceType:
return destination->kind() == Value::Kind::TypeType;
case Value::Kind::TypeOfClassType:
return destination->kind() == Value::Kind::TypeType;
case Value::Kind::TypeOfChoiceType:
// FIXME: These types should presumably also convert to interface types.
if (destination->kind() == Value::Kind::TypeType) {
return true;
}
break;
default:
return false;
break;
}
// If we weren't given an impl scope, only look for builtin conversions.
if (!impl_scope.has_value()) {
return false;
}
// We didn't find a builtin implicit conversion. Try a user-defined one.
// The source location doesn't matter, we're discarding the diagnostics.
SourceLocation source_loc("", 0);
ErrorOr<Nonnull<const InterfaceType*>> iface_type = GetBuiltinInterfaceType(
source_loc, BuiltinInterfaceName{Builtins::ImplicitAs, destination});
return iface_type.ok() &&
(*impl_scope)->Resolve(*iface_type, source, source_loc, *this).ok();
}
auto TypeChecker::ExpectType(SourceLocation source_loc,
const std::string& context,
Nonnull<const Value*> expected,
Nonnull<const Value*> actual) const
auto TypeChecker::ImplicitlyConvert(const std::string& context,
const ImplScope& impl_scope,
Nonnull<Expression*> source,
Nonnull<const Value*> destination)
-> ErrorOr<Nonnull<Expression*>> {
// FIXME: If a builtin conversion works, for now we don't create any
// expression to do the conversion and rely on the interpreter to know how to
// do it.
// FIXME: This doesn't work for cases of combined built-in and user-defined
// conversion, such as converting a struct element via an `ImplicitAs` impl.
if (IsImplicitlyConvertible(&source->static_type(), destination,
std::nullopt)) {
return source;
}
ErrorOr<Nonnull<Expression*>> converted = BuildBuiltinMethodCall(
impl_scope, source,
BuiltinInterfaceName{Builtins::ImplicitAs, destination},
BuiltinMethodCall{"Convert"});
if (!converted.ok()) {
// We couldn't find a matching `impl`.
return CompilationError(source->source_loc())
<< "type error in " << context << ": "
<< "'" << source->static_type()
<< "' is not implicitly convertible to '" << *destination << "'";
}
return *converted;
}
auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc,
BuiltinInterfaceName interface) const
-> ErrorOr<Nonnull<const InterfaceType*>> {
auto bad_builtin = [&]() -> Error {
return CompilationError(source_loc)
<< "unsupported declaration for builtin `"
<< Builtins::GetName(interface.builtin) << "`";
};
// Find the builtin interface declaration.
CARBON_ASSIGN_OR_RETURN(Nonnull<const Declaration*> builtin_decl,
builtins_.Get(source_loc, interface.builtin));
auto* iface_decl = dyn_cast<InterfaceDeclaration>(builtin_decl);
if (!iface_decl || !iface_decl->constant_value()) {
return bad_builtin();
}
// Match the interface arguments up with the parameters and build the
// interface type.
bool has_parameters = iface_decl->params().has_value();
bool has_arguments = !interface.arguments.empty();
if (has_parameters != has_arguments) {
return bad_builtin();
}
BindingMap bindings;
if (has_arguments) {
TupleValue args(interface.arguments);
if (!PatternMatch(&iface_decl->params().value()->value(), &args, source_loc,
std::nullopt, bindings, trace_stream_)) {
return bad_builtin();
}
}
return arena_->New<InterfaceType>(iface_decl, bindings);
}
auto TypeChecker::BuildBuiltinMethodCall(const ImplScope& impl_scope,
Nonnull<Expression*> source,
BuiltinInterfaceName interface,
BuiltinMethodCall method)
-> ErrorOr<Nonnull<Expression*>> {
const SourceLocation source_loc = source->source_loc();
CARBON_ASSIGN_OR_RETURN(Nonnull<const InterfaceType*> iface_type,
GetBuiltinInterfaceType(source_loc, interface));
// Build an expression to perform the call `source.(interface.method)(args)`.
Nonnull<Expression*> iface_expr = arena_->New<ValueLiteral>(
source_loc, iface_type, arena_->New<TypeOfInterfaceType>(iface_type),
ValueCategory::Let);
Nonnull<Expression*> iface_member =
arena_->New<FieldAccessExpression>(source_loc, iface_expr, method.name);
Nonnull<Expression*> method_access =
arena_->New<CompoundFieldAccessExpression>(source_loc, source,
iface_member);
Nonnull<Expression*> call_args =
arena_->New<TupleLiteral>(source_loc, method.arguments);
Nonnull<Expression*> call =
arena_->New<CallExpression>(source_loc, method_access, call_args);
CARBON_RETURN_IF_ERROR(TypeCheckExp(call, impl_scope));
return {call};
}
auto TypeChecker::ExpectType(
SourceLocation source_loc, const std::string& context,
Nonnull<const Value*> expected, Nonnull<const Value*> actual,
std::optional<Nonnull<const ImplScope*>> impl_scope) const
-> ErrorOr<Success> {
if (!IsImplicitlyConvertible(actual, expected)) {
if (!IsImplicitlyConvertible(actual, expected, impl_scope)) {
return CompilationError(source_loc)
<< "type error in " << context << ": "
<< "'" << *actual << "' is not implicitly convertible to '"
@@ -388,7 +534,8 @@ auto TypeChecker::ArgumentDeduction(
SourceLocation source_loc, const std::string& context,
llvm::ArrayRef<Nonnull<const GenericBinding*>> bindings_to_deduce,
BindingMap& deduced, Nonnull<const Value*> param, Nonnull<const Value*> arg,
bool allow_implicit_conversion) const -> ErrorOr<Success> {
bool allow_implicit_conversion, const ImplScope& impl_scope) const
-> ErrorOr<Success> {
if (trace_stream_) {
**trace_stream_ << "deducing " << *param << " from " << *arg << "\n";
}
@@ -407,7 +554,8 @@ auto TypeChecker::ArgumentDeduction(
}
const Value* subst_param_type = Substitute(deduced, param);
return allow_implicit_conversion
? ExpectType(source_loc, context, subst_param_type, arg)
? ExpectType(source_loc, context, subst_param_type, arg,
&impl_scope)
: ExpectExactType(source_loc, context, subst_param_type, arg);
};
@@ -443,7 +591,7 @@ auto TypeChecker::ArgumentDeduction(
CARBON_RETURN_IF_ERROR(
ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced,
param_tup.elements()[i], arg_tup.elements()[i],
allow_implicit_conversion));
allow_implicit_conversion, impl_scope));
}
return Success();
}
@@ -488,7 +636,7 @@ auto TypeChecker::ArgumentDeduction(
}
CARBON_RETURN_IF_ERROR(ArgumentDeduction(
source_loc, context, bindings_to_deduce, deduced, param_field.value,
arg_field.value, allow_implicit_conversion));
arg_field.value, allow_implicit_conversion, impl_scope));
}
if (param_struct.fields().size() != arg_struct.fields().size()) {
CARBON_CHECK(allow_implicit_conversion)
@@ -513,11 +661,11 @@ auto TypeChecker::ArgumentDeduction(
CARBON_RETURN_IF_ERROR(
ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced,
&param_fn.parameters(), &arg_fn.parameters(),
/*allow_implicit_conversion=*/false));
/*allow_implicit_conversion=*/false, impl_scope));
CARBON_RETURN_IF_ERROR(
ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced,
&param_fn.return_type(), &arg_fn.return_type(),
/*allow_implicit_conversion=*/false));
/*allow_implicit_conversion=*/false, impl_scope));
return Success();
}
case Value::Kind::PointerType: {
@@ -527,7 +675,7 @@ auto TypeChecker::ArgumentDeduction(
return ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced,
&cast<PointerType>(*param).type(),
&cast<PointerType>(*arg).type(),
/*allow_implicit_conversion=*/false);
/*allow_implicit_conversion=*/false, impl_scope);
}
// Nothing to do in the case for `auto`.
case Value::Kind::AutoType: {
@@ -549,7 +697,7 @@ auto TypeChecker::ArgumentDeduction(
CARBON_RETURN_IF_ERROR(
ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced,
param_ty, arg_class_type.type_args().at(ty),
/*allow_implicit_conversion=*/false));
/*allow_implicit_conversion=*/false, impl_scope));
}
return Success();
}
@@ -567,7 +715,7 @@ auto TypeChecker::ArgumentDeduction(
CARBON_RETURN_IF_ERROR(
ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced,
param_ty, arg_iface_type.args().at(ty),
/*allow_implicit_conversion=*/false));
/*allow_implicit_conversion=*/false, impl_scope));
}
return Success();
}
@@ -697,7 +845,6 @@ auto TypeChecker::Substitute(
case Value::Kind::TypeOfParameterizedEntityName:
case Value::Kind::TypeOfMemberName:
return type;
// The rest of these cases should never happen.
case Value::Kind::Witness:
case Value::Kind::ParameterizedEntityName:
case Value::Kind::MemberName:
@@ -714,7 +861,10 @@ auto TypeChecker::Substitute(
case Value::Kind::AlternativeConstructorValue:
case Value::Kind::ContinuationValue:
case Value::Kind::StringValue:
CARBON_FATAL() << "In Substitute: expected type, not value " << *type;
// This can happen when substituting into the arguments of a class or
// interface.
// TODO: Implement substitution for these cases.
return type;
}
}
@@ -735,7 +885,7 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface,
if (ErrorOr<Success> e = ArgumentDeduction(
source_loc, "match", impl.deduced, deduced_args, impl.type, impl_type,
/*allow_implicit_conversion=*/false);
/*allow_implicit_conversion=*/false, impl_scope);
!e.ok()) {
if (trace_stream_) {
**trace_stream_ << "type does not match: " << e.error() << "\n";
@@ -745,7 +895,7 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface,
if (ErrorOr<Success> e = ArgumentDeduction(
source_loc, "match", impl.deduced, deduced_args, impl.interface,
&iface, /*allow_implicit_conversion=*/false);
&iface, /*allow_implicit_conversion=*/false, impl_scope);
!e.ok()) {
if (trace_stream_) {
**trace_stream_ << "interface does not match: " << e.error() << "\n";
@@ -828,7 +978,7 @@ auto TypeChecker::DeduceCallBindings(
CARBON_RETURN_IF_ERROR(
ArgumentDeduction(arg->source_loc(), "call", deduced_bindings,
generic_bindings, param, &arg->static_type(),
/*allow_implicit_conversion=*/true));
/*allow_implicit_conversion=*/true, impl_scope));
// If the parameter is a `:!` binding, evaluate and collect its
// value for use in later parameters and in the function body.
if (!generic_params.empty() && generic_params.front().index == i) {
@@ -868,6 +1018,13 @@ auto TypeChecker::DeduceCallBindings(
impl_bindings, impl_scope, call.source_loc(), generic_bindings, impls));
call.set_impls(impls);
// Convert the arguments to the parameter type.
Nonnull<const Value*> param_type = Substitute(generic_bindings, params_type);
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_argument,
ImplicitlyConvert("call", impl_scope, &call.argument(), param_type));
call.set_argument(converted_argument);
return Success();
}
@@ -880,11 +1037,17 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
PrintConstants(**trace_stream_);
**trace_stream_ << "\n";
}
switch (e->kind()) {
case ExpressionKind::InstantiateImpl: {
CARBON_FATAL()
<< "instantiate impl nodes are generated during type checking";
if (e->is_type_checked()) {
if (trace_stream_) {
**trace_stream_ << "expression has already been type-checked\n";
}
return Success();
}
switch (e->kind()) {
case ExpressionKind::InstantiateImpl:
case ExpressionKind::ValueLiteral:
CARBON_FATAL() << "attempting to type check node " << *e
<< " generated during type checking";
case ExpressionKind::IndexExpression: {
auto& index = cast<IndexExpression>(*e);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&index.aggregate(), impl_scope));
@@ -1231,9 +1394,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
} else {
// This is `value.(member_name)`, where `member_name` specifies a type.
// `value` is implicitly converted to that type.
CARBON_RETURN_IF_ERROR(ExpectType(e->source_loc(),
"compound member access", *base_type,
&access.object().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_object,
ImplicitlyConvert("compound member access", impl_scope,
&access.object(), *base_type));
access.set_object(converted_object);
}
// Perform impl selection if necessary.
@@ -1525,10 +1690,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
return CompilationError(e->source_loc())
<< "__intrinsic_print takes 1 argument";
}
CARBON_RETURN_IF_ERROR(
ExpectType(e->source_loc(), "__intrinsic_print argument",
arena_->New<StringType>(),
&intrinsic_exp.args().fields()[0]->static_type()));
CARBON_RETURN_IF_ERROR(ExpectExactType(
e->source_loc(), "__intrinsic_print argument",
arena_->New<StringType>(),
&intrinsic_exp.args().fields()[0]->static_type()));
e->set_static_type(TupleValue::Empty());
e->set_value_category(ValueCategory::Let);
return Success();
@@ -1545,9 +1710,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
case ExpressionKind::IfExpression: {
auto& if_expr = cast<IfExpression>(*e);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&if_expr.condition(), impl_scope));
CARBON_RETURN_IF_ERROR(ExpectType(
if_expr.source_loc(), "condition of `if`", arena_->New<BoolType>(),
&if_expr.condition().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_condition,
ImplicitlyConvert("condition of `if`", impl_scope,
&if_expr.condition(), arena_->New<BoolType>()));
if_expr.set_condition(converted_condition);
// TODO: Compute the common type and convert both operands to it.
CARBON_RETURN_IF_ERROR(
@@ -1684,8 +1851,8 @@ auto TypeChecker::TypeCheckPattern(
CARBON_RETURN_IF_ERROR(ExpectIsType(binding.source_loc(), type));
if (expected) {
if (IsConcreteType(type)) {
CARBON_RETURN_IF_ERROR(
ExpectType(p->source_loc(), "name binding", type, *expected));
CARBON_RETURN_IF_ERROR(ExpectType(p->source_loc(), "name binding",
type, *expected, &impl_scope));
} else {
BindingMap generic_args;
if (!PatternMatch(type, *expected, binding.type().source_loc(),
@@ -1779,7 +1946,7 @@ auto TypeChecker::TypeCheckPattern(
if (expected) {
CARBON_RETURN_IF_ERROR(ExpectType(alternative.source_loc(),
"alternative pattern", &choice_type,
*expected));
*expected, &impl_scope));
}
std::optional<Nonnull<const Value*>> parameter_types =
choice_type.FindAlternative(alternative.alternative_name());
@@ -1833,23 +2000,46 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
auto& match = cast<Match>(*s);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope));
std::vector<Match::Clause> new_clauses;
std::optional<Nonnull<const Value*>> expected_type;
for (auto& clause : match.clauses()) {
ImplScope clause_scope;
clause_scope.AddParent(&impl_scope);
// FIXME: Should user-defined conversions be permitted in `match`
// statements? When would we run them? See #1283.
CARBON_RETURN_IF_ERROR(TypeCheckPattern(
&clause.pattern(), &match.expression().static_type(), clause_scope,
ValueCategory::Let));
if (expected_type.has_value()) {
// FIXME: For now, we require all patterns to have the same type. If
// that's not the same type as the scrutinee, we will convert the
// scrutinee. We might want to instead allow a different conversion
// to be performed for each pattern.
CARBON_RETURN_IF_ERROR(ExpectExactType(
clause.pattern().source_loc(), "`match` pattern type",
expected_type.value(), &clause.pattern().static_type()));
} else {
expected_type = &clause.pattern().static_type();
}
CARBON_RETURN_IF_ERROR(
TypeCheckStmt(&clause.statement(), clause_scope));
}
if (expected_type.has_value()) {
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_expression,
ImplicitlyConvert("`match` expression", impl_scope,
&match.expression(), expected_type.value()));
match.set_expression(converted_expression);
}
return Success();
}
case StatementKind::While: {
auto& while_stmt = cast<While>(*s);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&while_stmt.condition(), impl_scope));
CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "condition of `while`",
arena_->New<BoolType>(),
&while_stmt.condition().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_condition,
ImplicitlyConvert("condition of `while`", impl_scope,
&while_stmt.condition(), arena_->New<BoolType>()));
while_stmt.set_condition(converted_condition);
CARBON_RETURN_IF_ERROR(TypeCheckStmt(&while_stmt.body(), impl_scope));
return Success();
}
@@ -1878,19 +2068,26 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
var_scope.AddParent(&impl_scope);
CARBON_RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty,
var_scope, var.value_category()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_init,
ImplicitlyConvert("initializer of variable", impl_scope, &var.init(),
&var.pattern().static_type()));
var.set_init(converted_init);
return Success();
}
case StatementKind::Assign: {
auto& assign = cast<Assign>(*s);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&assign.rhs(), impl_scope));
CARBON_RETURN_IF_ERROR(TypeCheckExp(&assign.lhs(), impl_scope));
CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "assign",
&assign.lhs().static_type(),
&assign.rhs().static_type()));
if (assign.lhs().value_category() != ValueCategory::Var) {
return CompilationError(assign.source_loc())
<< "Cannot assign to rvalue '" << assign.lhs() << "'";
}
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_rhs,
ImplicitlyConvert("assignment", impl_scope, &assign.rhs(),
&assign.lhs().static_type()));
assign.set_rhs(converted_rhs);
return Success();
}
case StatementKind::ExpressionStatement: {
@@ -1901,9 +2098,11 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
case StatementKind::If: {
auto& if_stmt = cast<If>(*s);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&if_stmt.condition(), impl_scope));
CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "condition of `if`",
arena_->New<BoolType>(),
&if_stmt.condition().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_condition,
ImplicitlyConvert("condition of `if`", impl_scope,
&if_stmt.condition(), arena_->New<BoolType>()));
if_stmt.set_condition(converted_condition);
CARBON_RETURN_IF_ERROR(TypeCheckStmt(&if_stmt.then_block(), impl_scope));
if (if_stmt.else_block()) {
CARBON_RETURN_IF_ERROR(
@@ -1918,9 +2117,11 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
if (return_term.is_auto()) {
return_term.set_static_type(&ret.expression().static_type());
} else {
CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "return",
&return_term.static_type(),
&ret.expression().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_ret_val,
ImplicitlyConvert("return value", impl_scope, &ret.expression(),
&return_term.static_type()));
ret.set_expression(converted_ret_val);
}
return Success();
}
@@ -1933,9 +2134,11 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
case StatementKind::Run: {
auto& run = cast<Run>(*s);
CARBON_RETURN_IF_ERROR(TypeCheckExp(&run.argument(), impl_scope));
CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "argument of `run`",
arena_->New<ContinuationType>(),
&run.argument().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_argument,
ImplicitlyConvert("argument of `run`", impl_scope, &run.argument(),
arena_->New<ContinuationType>()));
run.set_argument(converted_argument);
return Success();
}
case StatementKind::Await: {
@@ -2384,9 +2587,11 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
binding_map[iface_decl.self()] = impl_type_value;
Nonnull<const Value*> iface_mem_type =
Substitute(binding_map, &m->static_type());
// FIXME: How should the signature in the implementation be permitted
// to differ from the signature in the interface?
CARBON_RETURN_IF_ERROR(
ExpectType((*mem)->source_loc(), "member of implementation",
iface_mem_type, &(*mem)->static_type()));
ExpectExactType((*mem)->source_loc(), "member of implementation",
iface_mem_type, &(*mem)->static_type()));
} else {
return CompilationError(impl_decl->source_loc())
<< "implementation missing " << *mem_name;
@@ -2520,6 +2725,9 @@ auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr<Success> {
}
for (Nonnull<Declaration*> decl : ast.declarations) {
CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(decl, impl_scope));
// Check to see if this declaration is a builtin.
// FIXME: Only do this when type-checking the prelude.
builtins_.Register(decl);
}
CARBON_RETURN_IF_ERROR(TypeCheckExp(*ast.main_call, impl_scope));
return Success();
@@ -2553,9 +2761,6 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d,
return Success();
case DeclarationKind::VariableDeclaration: {
auto& var = cast<VariableDeclaration>(*d);
// Signals a type error if the initializing expression does not have
// the declared type of the variable, otherwise returns this
// declaration with annotated types.
if (var.has_initializer()) {
CARBON_RETURN_IF_ERROR(TypeCheckExp(&var.initializer(), impl_scope));
}
@@ -2567,9 +2772,11 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d,
<< "Type of a top-level variable must be an expression.";
}
if (var.has_initializer()) {
CARBON_RETURN_IF_ERROR(
ExpectType(var.source_loc(), "initializer of variable",
&var.static_type(), &var.initializer().static_type()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<Expression*> converted_initializer,
ImplicitlyConvert("initializer of variable", impl_scope,
&var.initializer(), &var.static_type()));
var.set_initializer(converted_initializer);
}
return Success();
}
+55 -8
View File
@@ -12,6 +12,7 @@
#include "explorer/ast/expression.h"
#include "explorer/ast/statement.h"
#include "explorer/common/nonnull.h"
#include "explorer/interpreter/builtins.h"
#include "explorer/interpreter/dictionary.h"
#include "explorer/interpreter/impl_scope.h"
#include "explorer/interpreter/interpreter.h"
@@ -38,12 +39,15 @@ class TypeChecker {
// parameterized type.
// The `deduced` parameter is an accumulator, that is, it holds the
// results so-far.
// `allow_implicit_conversion` specifies whether implicit conversions are
// permitted from the argument to the parameter type. If so, an `impl_scope`
// must be provided.
auto ArgumentDeduction(
SourceLocation source_loc, const std::string& context,
llvm::ArrayRef<Nonnull<const GenericBinding*>> bindings_to_deduce,
BindingMap& deduced, Nonnull<const Value*> param,
Nonnull<const Value*> arg, bool allow_implicit_conversion) const
-> ErrorOr<Success>;
Nonnull<const Value*> arg, bool allow_implicit_conversion,
const ImplScope& impl_scope) const -> ErrorOr<Success>;
// If `impl` can be an implementation of interface `iface` for the
// given `type`, then return an expression that will produce the witness
@@ -92,8 +96,8 @@ class TypeChecker {
// Equivalent to TypeCheckExp, but operates on the AST rooted at `p`.
//
// `expected` is the type that this pattern is expected to have, if the
// surrounding context gives us that information. Otherwise, it is
// nullopt.
// surrounding context gives us that information. Otherwise, it is nullopt.
// Implicit conversions from `expected` to the pattern's type are permitted.
//
// `impl_scope` is extended with all impls implied by the pattern.
auto TypeCheckPattern(Nonnull<Pattern*> p,
@@ -244,14 +248,56 @@ class TypeChecker {
// Returns true if *source is implicitly convertible to *destination. *source
// and *destination must be concrete types.
auto IsImplicitlyConvertible(Nonnull<const Value*> source,
Nonnull<const Value*> destination) const -> bool;
auto IsImplicitlyConvertible(
Nonnull<const Value*> source, Nonnull<const Value*> destination,
std::optional<Nonnull<const ImplScope*>> impl_scope) const -> bool;
// Attempt to implicitly convert type-checked expression `source` to the type
// `destination`.
auto ImplicitlyConvert(const std::string& context,
const ImplScope& impl_scope,
Nonnull<Expression*> source,
Nonnull<const Value*> destination)
-> ErrorOr<Nonnull<Expression*>>;
// Check whether `actual` is implicitly convertible to `expected`
// and halt with a fatal compilation error if it is not.
//
// If `impl_scope` is `std::nullopt`, only built-in conversions are
// considered.
// FIXME: Remove this behavior.
//
// FIXME: Does not actually perform the conversion if a user-defined
// conversion is needed. Should be used very rarely for that reason.
auto ExpectType(SourceLocation source_loc, const std::string& context,
Nonnull<const Value*> expected,
Nonnull<const Value*> actual) const -> ErrorOr<Success>;
Nonnull<const Value*> expected, Nonnull<const Value*> actual,
std::optional<Nonnull<const ImplScope*>> impl_scope) const
-> ErrorOr<Success>;
// The name of a builtin interface, with any arguments.
struct BuiltinInterfaceName {
Builtins::Builtin builtin;
llvm::ArrayRef<Nonnull<const Value*>> arguments = {};
};
// The name of a method on a builtin interface, with any arguments.
struct BuiltinMethodCall {
const std::string& name;
llvm::ArrayRef<Nonnull<Expression*>> arguments = {};
};
// Form a builtin method call. Ensures that the type of `source` implements
// the interface `interface`, which should be defined in the prelude, and
// forms a call to the method `method` on that interface.
auto BuildBuiltinMethodCall(const ImplScope& impl_scope,
Nonnull<Expression*> source,
BuiltinInterfaceName interface,
BuiltinMethodCall method)
-> ErrorOr<Nonnull<Expression*>>;
// Get a type for a builtin interface.
auto GetBuiltinInterfaceType(SourceLocation source_loc,
BuiltinInterfaceName interface) const
-> ErrorOr<Nonnull<const InterfaceType*>>;
// Construct a type that is the same as `type` except that occurrences
// of type variables (aka. `GenericBinding`) are replaced by their
@@ -279,6 +325,7 @@ class TypeChecker {
Nonnull<Arena*> arena_;
std::set<ValueNodeView> constants_;
Builtins builtins_;
std::optional<Nonnull<llvm::raw_ostream*>> trace_stream_;
};
+25
View File
@@ -0,0 +1,25 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 1
package ExplorerTest api;
class A {
var n: i32;
}
impl i32 as ImplicitAs(A) {
fn Convert[me: Self]() -> A { return {.n = me }; }
}
fn Main() -> i32 {
var a: A = {.n = 0};
a = 1;
return a.n;
}
+1 -1
View File
@@ -22,7 +22,7 @@ fn Main() -> i32 {
var y: Ints = Ints.One(42);
var n: i32 = 0;
match (y) {
case Ints.None =>
case Ints.None() =>
n = n + 2;
case Ints.One(x: auto) =>
n = x + 1 - 42;
@@ -0,0 +1,27 @@
// 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
//
// RUN: %{not} %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
package ExplorerTest api;
choice Ints {
None,
One(i32),
Two(i32, i32)
}
fn Main() -> i32 {
match (Ints.None()) {
case Ints.None => return 0;
// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_choice_no_parens.carbon:[[@LINE+3]]: type error in `match` pattern type
// CHECK: expected: fn () -> choice Ints
// CHECK: actual: choice Ints
default => return 1;
}
}
@@ -0,0 +1,33 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 3
package ExplorerTest api;
class Wrap(T:! Type) {
var v: T;
}
fn MakeWrap[T:! Type](x: T) -> Wrap(T) { return {.v = x}; }
impl forall [T:! Type] Wrap(T) as ImplicitAs(T) {
fn Convert[me: Self]() -> T {
return me.v;
}
}
fn Main() -> i32 {
var n: i32 = 1;
__continuation k {
n = n + 1;
}
n = 2;
__run MakeWrap(k);
return n;
}
+42
View File
@@ -0,0 +1,42 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 123
package ExplorerTest api;
class One {
impl One as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return 1; }
}
}
class Two {
impl Two as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return 2; }
}
}
class N {
impl N as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return me.n; }
}
var n: i32;
}
fn Get(a: i32, b: i32, c: i32) -> i32 {
return 100 * a + 10 * b + c;
}
fn Main() -> i32 {
var i: One = {};
var ii: Two = {};
var iii: N = {.n = 3};
return Get(i, ii, iii);
}
@@ -0,0 +1,25 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 5
package ExplorerTest api;
class A {
var n: i32;
}
impl i32 as ImplicitAs(A) {
fn Convert[me: Self]() -> A { return {.n = me}; }
}
var a: A = 5;
fn Main() -> i32 {
return a.n;
}
+33
View File
@@ -0,0 +1,33 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 1
package ExplorerTest api;
class LazyEq {
var v1: i32;
var v2: i32;
impl as ImplicitAs(Bool) {
fn Convert[me: Self]() -> Bool {
return me.v1 == me.v2;
}
}
fn Make(v1: i32, v2: i32) -> Self {
return {.v1 = v1, .v2 = v2};
}
}
fn Main() -> i32 {
if (LazyEq.Make(2, 1 + 1)) {
return 1;
} else {
return 2;
}
}
@@ -0,0 +1,29 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 1
package ExplorerTest api;
class LazyEq {
var v1: i32;
var v2: i32;
impl as ImplicitAs(Bool) {
fn Convert[me: Self]() -> Bool {
return me.v1 == me.v2;
}
}
fn Make(v1: i32, v2: i32) -> Self {
return {.v1 = v1, .v2 = v2};
}
}
fn Main() -> i32 {
return if LazyEq.Make(2, 1 + 1) then 1 else 2;
}
+3 -1
View File
@@ -24,7 +24,9 @@ class Point {
}
fn Scale[me: Point](v: i32) -> i32 {
return 0;
// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_bad_member.carbon:[[@LINE+1]]: type error in member of implementation: 'fn (i32) -> i32' is not implicitly convertible to 'fn (i32) -> class Point'
// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_bad_member.carbon:[[@LINE+3]]: type error in member of implementation
// CHECK: expected: fn (i32) -> class Point
// CHECK: actual: fn (i32) -> i32
}
}
}
+26
View File
@@ -0,0 +1,26 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 3
package ExplorerTest api;
class A {
impl as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return me.n; }
}
var n: i32;
}
fn Main() -> i32 {
let a: A = {.n = 1};
let b: A = {.n = 2};
let (x: i32, var y: i32) = (a, b);
return x + y;
}
+25
View File
@@ -0,0 +1,25 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 3
package ExplorerTest api;
choice A {
Value(i32)
}
impl i32 as ImplicitAs(A) {
fn Convert[me: Self]() -> A { return A.Value(me + 1); }
}
fn Main() -> i32 {
let A.Value(n: i32) = 2;
return n;
}
+29
View File
@@ -0,0 +1,29 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 0
package ExplorerTest api;
class A {}
impl A as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return 1; }
}
fn Main() -> i32 {
var a: A = {};
match ((a, a)) {
case (0, n: i32) => return 1;
case (n: i32, 0) => return 1;
case (1, 1) => return 0;
}
return 1;
}
@@ -0,0 +1,34 @@
// 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
//
// RUN: %{not} %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
package ExplorerTest api;
class A {}
impl A as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return 1; }
}
fn Main() -> i32 {
var a: A = {};
// FIXME: It's not clear whether this should be valid. The patterns here have
// different types, but we could perform different conversions on the source
// expression when checking each pattern.
match ((a, a)) {
case (0, n: A) => return 1;
// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/match/fail_pattern_type_mismatch.carbon:[[@LINE+3]]: type error in `match` pattern type
// CHECK: expected: (i32, class A)
// CHECK: actual: (class A, i32)
case (n: A, 0) => return 1;
case (1, 1) => return 0;
}
return 1;
}
@@ -0,0 +1,30 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 3
package Foo api;
class X {
fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; }
var n: i32;
}
class Y {
var m: i32;
impl as ImplicitAs(X) {
fn Convert[me: Self]() -> X { return {.n = me.m}; }
}
}
fn Main() -> i32 {
var y1: Y = {.m = 1};
var y2: Y = {.m = 2};
return y1.(X.F)(y2).n;
}
@@ -0,0 +1,36 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 3
package Foo api;
interface HasF {
fn F[me: Self](o: Self) -> Self;
}
class X {
impl as HasF {
fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; }
}
var n: i32;
}
class Y {
var m: i32;
impl as ImplicitAs(X) {
fn Convert[me: Self]() -> X { return {.n = me.m}; }
}
}
fn Main() -> i32 {
var x: X = {.n = 1};
var y: Y = {.m = 2};
return y.(X.(HasF.F))(x).n;
}
+23
View File
@@ -0,0 +1,23 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 42
package ExplorerTest api;
class A {
impl as ImplicitAs(i32) {
fn Convert[me: Self]() -> i32 { return 42; }
}
}
fn Main() -> i32 {
var a: A = {};
return a;
}
+33
View File
@@ -0,0 +1,33 @@
// 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
//
// RUN: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 0
package ExplorerTest api;
class LazyNe {
var v1: i32;
var v2: i32;
impl as ImplicitAs(Bool) {
fn Convert[me: Self]() -> Bool {
return not (me.v1 == me.v2);
}
}
fn Make(v1: i32, v2: i32) -> Self {
return {.v1 = v1, .v2 = v2};
}
}
fn Main() -> i32 {
var x: auto = 2;
while (LazyNe.Make(x, 0)) {
x = x - 1;
}
return x;
}