mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 09:24:46 +01:00
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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
¶m_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,
|
||||
¶m_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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user