mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:40:12 +01:00
Make built-in conversions visible to ImplicitAs. (#2525)
Add a blanket `ImplicitAs` implementation to perform the conversions that explorer can perform as built-in conversions. This allows those conversions to be detected by constraints and to be used as part of other user-defined conversions. For now, a single monolithic conversion is exposed. I intend to split this up into multiple smaller conversion kinds for each kind of conversion in a follow-up change. Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
This commit is contained in:
co-authored by
Jon Ross-Perkins
parent
94872ef6da
commit
0d279b388a
@@ -31,6 +31,8 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name,
|
||||
{"new", Intrinsic::Alloc},
|
||||
{"delete", Intrinsic::Dealloc},
|
||||
{"rand", Intrinsic::Rand},
|
||||
{"implicit_as", Intrinsic::ImplicitAs},
|
||||
{"implicit_as_convert", Intrinsic::ImplicitAsConvert},
|
||||
{"int_eq", Intrinsic::IntEq},
|
||||
{"int_compare", Intrinsic::IntCompare},
|
||||
{"int_bit_complement", Intrinsic::IntBitComplement},
|
||||
@@ -61,6 +63,10 @@ auto IntrinsicExpression::name() const -> std::string_view {
|
||||
return "__intrinsic_delete";
|
||||
case IntrinsicExpression::Intrinsic::Rand:
|
||||
return "__intrinsic_rand";
|
||||
case IntrinsicExpression::Intrinsic::ImplicitAs:
|
||||
return "__intrinsic_implicit_as";
|
||||
case IntrinsicExpression::Intrinsic::ImplicitAsConvert:
|
||||
return "__intrinsic_implicit_as_convert";
|
||||
case IntrinsicExpression::Intrinsic::IntEq:
|
||||
return "__intrinsic_int_eq";
|
||||
case IntrinsicExpression::Intrinsic::IntCompare:
|
||||
|
||||
@@ -751,6 +751,8 @@ class IntrinsicExpression : public Expression {
|
||||
Alloc,
|
||||
Dealloc,
|
||||
Rand,
|
||||
ImplicitAs,
|
||||
ImplicitAsConvert,
|
||||
IntEq,
|
||||
StrEq,
|
||||
StrCompare,
|
||||
|
||||
@@ -30,33 +30,42 @@ impl forall [U:! type] U as __EqualConverter where .T = U {
|
||||
fn Convert(u: U) -> U { return u; }
|
||||
}
|
||||
|
||||
// Every type implicitly converts to single-step-equal types.
|
||||
impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) {
|
||||
fn Convert[self: Self]() -> U { return __EqualConvert(self, U); }
|
||||
}
|
||||
__match_first {
|
||||
// Pick up implicit conversions that are built into the compiler.
|
||||
// TODO: Split these into individual categories and implement as many as we can
|
||||
// in the prelude.
|
||||
impl forall [U:! type, T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) {
|
||||
fn Convert[self: Self]() -> U { return __intrinsic_implicit_as_convert(self, U); }
|
||||
}
|
||||
|
||||
// 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[self: Self]() -> (U1,) {
|
||||
let (v1: T1,) = self;
|
||||
return (v1.Convert(),);
|
||||
// Every type implicitly converts to single-step-equal types.
|
||||
impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) {
|
||||
fn Convert[self: Self]() -> U { return __EqualConvert(self, U); }
|
||||
}
|
||||
}
|
||||
impl forall [U1:! type, U2:! type, T1:! ImplicitAs(U1), T2:! ImplicitAs(U2)]
|
||||
(T1, T2) as ImplicitAs((U1, U2)) {
|
||||
fn Convert[self: Self]() -> (U1, U2) {
|
||||
let (v1: T1, v2: T2) = self;
|
||||
return (v1.Convert(), v2.Convert());
|
||||
|
||||
// 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[self: Self]() -> (U1,) {
|
||||
let (v1: T1,) = self;
|
||||
return (v1.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[self: Self]() -> (U1, U2, U3) {
|
||||
let (v1: T1, v2: T2, v3: T3) = self;
|
||||
return (v1.Convert(), v2.Convert(), v3.Convert());
|
||||
impl forall [U1:! type, U2:! type, T1:! ImplicitAs(U1), T2:! ImplicitAs(U2)]
|
||||
(T1, T2) as ImplicitAs((U1, U2)) {
|
||||
fn Convert[self: Self]() -> (U1, U2) {
|
||||
let (v1: T1, v2: T2) = self;
|
||||
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[self: Self]() -> (U1, U2, U3) {
|
||||
let (v1: T1, v2: T2, v3: T3) = self;
|
||||
return (v1.Convert(), v2.Convert(), v3.Convert());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,13 +128,15 @@ auto ImplScope::Resolve(Nonnull<const Value*> constraint_type,
|
||||
witnesses.push_back(result);
|
||||
}
|
||||
|
||||
// Check that all equality and rewrite constraints are satisfied in this
|
||||
// scope.
|
||||
// Check that all intrinsic, equality, and rewrite constraints
|
||||
// are satisfied in this scope.
|
||||
llvm::ArrayRef<IntrinsicConstraint> intrinsics =
|
||||
constraint->intrinsic_constraints();
|
||||
llvm::ArrayRef<EqualityConstraint> equals =
|
||||
constraint->equality_constraints();
|
||||
llvm::ArrayRef<RewriteConstraint> rewrites =
|
||||
constraint->rewrite_constraints();
|
||||
if (!equals.empty() || !rewrites.empty()) {
|
||||
if (!intrinsics.empty() || !equals.empty() || !rewrites.empty()) {
|
||||
std::optional<Nonnull<const Witness*>> witness;
|
||||
if (constraint->self_binding()->impl_binding()) {
|
||||
witness = type_checker.MakeConstraintWitness(witnesses);
|
||||
@@ -142,6 +144,21 @@ auto ImplScope::Resolve(Nonnull<const Value*> constraint_type,
|
||||
Bindings local_bindings = bindings;
|
||||
local_bindings.Add(constraint->self_binding(), impl_type, witness);
|
||||
SingleStepEqualityContext equality_ctx(this);
|
||||
for (const auto& intrinsic : intrinsics) {
|
||||
IntrinsicConstraint converted = {
|
||||
.type = type_checker.Substitute(local_bindings, intrinsic.type),
|
||||
.kind = intrinsic.kind,
|
||||
.arguments = {}};
|
||||
converted.arguments.reserve(intrinsic.arguments.size());
|
||||
for (Nonnull<const Value*> argument : intrinsic.arguments) {
|
||||
converted.arguments.push_back(
|
||||
type_checker.Substitute(local_bindings, argument));
|
||||
}
|
||||
if (!type_checker.IsIntrinsicConstraintSatisfied(converted, *this)) {
|
||||
return ProgramError(source_loc)
|
||||
<< "constraint requires that " << converted;
|
||||
}
|
||||
}
|
||||
for (const auto& equal : equals) {
|
||||
auto it = equal.values.begin();
|
||||
Nonnull<const Value*> first =
|
||||
|
||||
@@ -1467,6 +1467,42 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
|
||||
int r = (generator() % (high - low)) + low;
|
||||
return todo_.FinishAction(arena_->New<IntValue>(r));
|
||||
}
|
||||
case IntrinsicExpression::Intrinsic::ImplicitAs: {
|
||||
CARBON_CHECK(args.size() == 1);
|
||||
// Build a constraint type that constrains its .Self type to satisfy
|
||||
// the "ImplicitAs" intrinsic constraint. This involves creating a
|
||||
// number of objects that all point to each other.
|
||||
// TODO: Factor out a simple version of ConstraintTypeBuilder and use
|
||||
// it from here.
|
||||
auto* self_binding = arena_->New<GenericBinding>(
|
||||
exp.source_loc(), ".Self",
|
||||
arena_->New<TypeTypeLiteral>(exp.source_loc()));
|
||||
auto* self = arena_->New<VariableType>(self_binding);
|
||||
auto* impl_binding = arena_->New<ImplBinding>(
|
||||
exp.source_loc(), self_binding, std::nullopt);
|
||||
impl_binding->set_symbolic_identity(
|
||||
arena_->New<BindingWitness>(impl_binding));
|
||||
self_binding->set_symbolic_identity(self);
|
||||
self_binding->set_value(self);
|
||||
self_binding->set_impl_binding(impl_binding);
|
||||
IntrinsicConstraint constraint = {
|
||||
.type = self,
|
||||
.kind = IntrinsicConstraint::ImplicitAs,
|
||||
.arguments = args};
|
||||
auto* result = arena_->New<ConstraintType>(
|
||||
self_binding, std::vector<ImplConstraint>{},
|
||||
std::vector<IntrinsicConstraint>{std::move(constraint)},
|
||||
std::vector<EqualityConstraint>{},
|
||||
std::vector<RewriteConstraint>{}, std::vector<LookupContext>{});
|
||||
impl_binding->set_interface(result);
|
||||
return todo_.FinishAction(result);
|
||||
}
|
||||
case IntrinsicExpression::Intrinsic::ImplicitAsConvert: {
|
||||
CARBON_CHECK(args.size() == 2);
|
||||
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> result,
|
||||
Convert(args[0], args[1], exp.source_loc()));
|
||||
return todo_.FinishAction(result);
|
||||
}
|
||||
case IntrinsicExpression::Intrinsic::IntEq: {
|
||||
CARBON_CHECK(args.size() == 2);
|
||||
auto lhs = cast<IntValue>(*args[0]).value();
|
||||
|
||||
@@ -729,6 +729,20 @@ auto TypeChecker::ImplicitlyConvert(std::string_view context,
|
||||
return *converted;
|
||||
}
|
||||
|
||||
auto TypeChecker::IsIntrinsicConstraintSatisfied(
|
||||
const IntrinsicConstraint& constraint, const ImplScope& impl_scope) const
|
||||
-> bool {
|
||||
// TODO: Check to see if this constraint is known in the current impl scope.
|
||||
switch (constraint.kind) {
|
||||
case IntrinsicConstraint::ImplicitAs:
|
||||
CARBON_CHECK(constraint.arguments.size() == 1)
|
||||
<< "wrong number of arguments for `__intrinsic_implicit_as`";
|
||||
return IsImplicitlyConvertible(constraint.type, constraint.arguments[0],
|
||||
impl_scope,
|
||||
/*allow_user_defined_conversions=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc,
|
||||
BuiltinInterfaceName interface) const
|
||||
-> ErrorOr<Nonnull<const InterfaceType*>> {
|
||||
@@ -1397,6 +1411,12 @@ class TypeChecker::ConstraintTypeBuilder {
|
||||
return impl_constraints_.size() - 1;
|
||||
}
|
||||
|
||||
// Adds an intrinsic constraint, if not already present.
|
||||
void AddIntrinsicConstraint(IntrinsicConstraint intrinsic) {
|
||||
// TODO: Consider performing deduplication.
|
||||
intrinsic_constraints_.push_back(std::move(intrinsic));
|
||||
}
|
||||
|
||||
// Adds an equality constraint -- `A == B`.
|
||||
void AddEqualityConstraint(EqualityConstraint equal) {
|
||||
if (equal.values.size() < 2) {
|
||||
@@ -1514,6 +1534,21 @@ class TypeChecker::ConstraintTypeBuilder {
|
||||
AddEqualityConstraint({.values = std::move(values)});
|
||||
}
|
||||
|
||||
for (const auto& intrinsic_constraint :
|
||||
constraint->intrinsic_constraints()) {
|
||||
IntrinsicConstraint converted = {
|
||||
.type = type_checker.Substitute(local_bindings,
|
||||
intrinsic_constraint.type),
|
||||
.kind = intrinsic_constraint.kind,
|
||||
.arguments = {}};
|
||||
converted.arguments.reserve(intrinsic_constraint.arguments.size());
|
||||
for (Nonnull<const Value*> argument : intrinsic_constraint.arguments) {
|
||||
converted.arguments.push_back(
|
||||
type_checker.Substitute(local_bindings, argument));
|
||||
}
|
||||
AddIntrinsicConstraint(std::move(converted));
|
||||
}
|
||||
|
||||
if (add_lookup_contexts) {
|
||||
for (const auto& lookup_context : constraint->lookup_contexts()) {
|
||||
AddLookupContext({.context = type_checker.Substitute(
|
||||
@@ -1578,8 +1613,8 @@ class TypeChecker::ConstraintTypeBuilder {
|
||||
// Create the new type.
|
||||
auto* result = arena_->New<ConstraintType>(
|
||||
self_binding_, std::move(impl_constraints_),
|
||||
std::move(equality_constraints_), std::move(rewrite_constraints_),
|
||||
std::move(lookup_contexts_));
|
||||
std::move(intrinsic_constraints_), std::move(equality_constraints_),
|
||||
std::move(rewrite_constraints_), std::move(lookup_contexts_));
|
||||
// Update the impl binding to denote the constraint type itself.
|
||||
impl_binding_->set_interface(result);
|
||||
return result;
|
||||
@@ -1740,6 +1775,15 @@ class TypeChecker::ConstraintTypeBuilder {
|
||||
type_checker.RebuildValue(impl_constraint.interface));
|
||||
}
|
||||
|
||||
// Apply rewrites throughout intrinsic constraints.
|
||||
for (auto& intrinsic_constraint : intrinsic_constraints_) {
|
||||
intrinsic_constraint.type =
|
||||
type_checker.RebuildValue(intrinsic_constraint.type);
|
||||
for (auto& argument : intrinsic_constraint.arguments) {
|
||||
argument = type_checker.RebuildValue(argument);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply rewrites throughout equality constraints.
|
||||
for (auto& equality_constraint : equality_constraints_) {
|
||||
for (auto*& value : equality_constraint.values) {
|
||||
@@ -1758,6 +1802,7 @@ class TypeChecker::ConstraintTypeBuilder {
|
||||
Nonnull<GenericBinding*> self_binding_;
|
||||
Nonnull<ImplBinding*> impl_binding_;
|
||||
std::vector<ImplConstraint> impl_constraints_;
|
||||
std::vector<IntrinsicConstraint> intrinsic_constraints_;
|
||||
std::vector<EqualityConstraint> equality_constraints_;
|
||||
std::vector<RewriteConstraint> rewrite_constraints_;
|
||||
std::vector<LookupContext> lookup_contexts_;
|
||||
@@ -3342,7 +3387,30 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
|
||||
&args[1]->static_type(), impl_scope));
|
||||
|
||||
e->set_static_type(arena_->New<IntType>());
|
||||
|
||||
e->set_value_category(ValueCategory::Let);
|
||||
return Success();
|
||||
}
|
||||
case IntrinsicExpression::Intrinsic::ImplicitAs: {
|
||||
if (args.size() != 1) {
|
||||
return ProgramError(e->source_loc())
|
||||
<< "__intrinsic_implicit_as takes 1 argument";
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(args[0], impl_scope));
|
||||
e->set_static_type(arena_->New<TypeType>());
|
||||
e->set_value_category(ValueCategory::Let);
|
||||
return Success();
|
||||
}
|
||||
case IntrinsicExpression::Intrinsic::ImplicitAsConvert: {
|
||||
if (args.size() != 2) {
|
||||
return ProgramError(e->source_loc())
|
||||
<< "__intrinsic_implicit_as_convert takes 2 arguments";
|
||||
}
|
||||
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> result,
|
||||
TypeCheckTypeExp(args[1], impl_scope));
|
||||
// TODO: Check that the type of args[0] implicitly converts to
|
||||
// args[1].
|
||||
e->set_static_type(result);
|
||||
e->set_value_category(ValueCategory::Let);
|
||||
return Success();
|
||||
}
|
||||
case IntrinsicExpression::Intrinsic::IntEq: {
|
||||
|
||||
@@ -92,6 +92,12 @@ class TypeChecker {
|
||||
int impl_offset) const
|
||||
-> Nonnull<const Witness*>;
|
||||
|
||||
// Determine whether the given intrinsic constraint is known to be satisfied
|
||||
// in the given scope.
|
||||
auto IsIntrinsicConstraintSatisfied(const IntrinsicConstraint& constraint,
|
||||
const ImplScope& impl_scope) const
|
||||
-> bool;
|
||||
|
||||
private:
|
||||
class ConstraintTypeBuilder;
|
||||
class SubstitutedGenericBindings;
|
||||
|
||||
@@ -698,6 +698,23 @@ void Value::Print(llvm::raw_ostream& out) const {
|
||||
}
|
||||
}
|
||||
|
||||
void IntrinsicConstraint::Print(llvm::raw_ostream& out) const {
|
||||
out << *type << " is ";
|
||||
switch (kind) {
|
||||
case IntrinsicConstraint::ImplicitAs:
|
||||
out << "__intrinsic_implicit_as";
|
||||
break;
|
||||
}
|
||||
if (!arguments.empty()) {
|
||||
out << "(";
|
||||
llvm::ListSeparator comma;
|
||||
for (Nonnull<const Value*> argument : arguments) {
|
||||
out << comma << *argument;
|
||||
}
|
||||
out << ")";
|
||||
}
|
||||
}
|
||||
|
||||
ContinuationValue::StackFragment::~StackFragment() {
|
||||
CARBON_CHECK(reversed_todo_.empty())
|
||||
<< "All StackFragments must be empty before the Carbon program ends.";
|
||||
|
||||
@@ -935,6 +935,26 @@ struct ImplConstraint {
|
||||
Nonnull<const InterfaceType*> interface;
|
||||
};
|
||||
|
||||
// A constraint that requires an intrinsic property of a type.
|
||||
struct IntrinsicConstraint {
|
||||
// Print the intrinsic constraint.
|
||||
void Print(llvm::raw_ostream& out) const;
|
||||
|
||||
// The type that is required to satisfy the intrinsic property.
|
||||
Nonnull<const Value*> type;
|
||||
// The kind of the intrinsic property.
|
||||
enum Kind {
|
||||
// `type` intrinsically implicitly converts to `parameters[0]`.
|
||||
// TODO: Split ImplicitAs into more specific constraints (such as
|
||||
// derived-to-base pointer conversions).
|
||||
ImplicitAs,
|
||||
};
|
||||
Kind kind;
|
||||
// Arguments for the intrinsic property. The meaning of these depends on
|
||||
// `kind`.
|
||||
std::vector<Nonnull<const Value*>> arguments;
|
||||
};
|
||||
|
||||
// A constraint that a collection of values are known to be the same.
|
||||
struct EqualityConstraint {
|
||||
// Visit the values in this equality constraint that are a single step away
|
||||
@@ -979,6 +999,8 @@ struct LookupContext {
|
||||
//
|
||||
// * A collection of (type, interface) pairs for interfaces that are known to
|
||||
// be implemented by a type satisfying the constraint.
|
||||
// * A collection of (type, intrinsic) pairs for intrinsic properties that are
|
||||
// known to be satisfied by a type satisfying the constraint.
|
||||
// * A collection of sets of values, typically associated constants, that are
|
||||
// known to be the same.
|
||||
// * A collection of contexts in which member name lookups will be performed
|
||||
@@ -988,14 +1010,17 @@ struct LookupContext {
|
||||
// `VariableType` naming the `self_binding`.
|
||||
class ConstraintType : public Value {
|
||||
public:
|
||||
explicit ConstraintType(Nonnull<const GenericBinding*> self_binding,
|
||||
std::vector<ImplConstraint> impl_constraints,
|
||||
std::vector<EqualityConstraint> equality_constraints,
|
||||
std::vector<RewriteConstraint> rewrite_constraints,
|
||||
std::vector<LookupContext> lookup_contexts)
|
||||
explicit ConstraintType(
|
||||
Nonnull<const GenericBinding*> self_binding,
|
||||
std::vector<ImplConstraint> impl_constraints,
|
||||
std::vector<IntrinsicConstraint> intrinsic_constraints,
|
||||
std::vector<EqualityConstraint> equality_constraints,
|
||||
std::vector<RewriteConstraint> rewrite_constraints,
|
||||
std::vector<LookupContext> lookup_contexts)
|
||||
: Value(Kind::ConstraintType),
|
||||
self_binding_(self_binding),
|
||||
impl_constraints_(std::move(impl_constraints)),
|
||||
intrinsic_constraints_(std::move(intrinsic_constraints)),
|
||||
equality_constraints_(std::move(equality_constraints)),
|
||||
rewrite_constraints_(std::move(rewrite_constraints)),
|
||||
lookup_contexts_(std::move(lookup_contexts)) {}
|
||||
@@ -1006,8 +1031,8 @@ class ConstraintType : public Value {
|
||||
|
||||
template <typename F>
|
||||
auto Decompose(F f) const {
|
||||
return f(self_binding_, impl_constraints_, equality_constraints_,
|
||||
rewrite_constraints_, lookup_contexts_);
|
||||
return f(self_binding_, impl_constraints_, intrinsic_constraints_,
|
||||
equality_constraints_, rewrite_constraints_, lookup_contexts_);
|
||||
}
|
||||
|
||||
auto self_binding() const -> Nonnull<const GenericBinding*> {
|
||||
@@ -1018,6 +1043,10 @@ class ConstraintType : public Value {
|
||||
return impl_constraints_;
|
||||
}
|
||||
|
||||
auto intrinsic_constraints() const -> llvm::ArrayRef<IntrinsicConstraint> {
|
||||
return intrinsic_constraints_;
|
||||
}
|
||||
|
||||
auto equality_constraints() const -> llvm::ArrayRef<EqualityConstraint> {
|
||||
return equality_constraints_;
|
||||
}
|
||||
@@ -1044,6 +1073,7 @@ class ConstraintType : public Value {
|
||||
private:
|
||||
Nonnull<const GenericBinding*> self_binding_;
|
||||
std::vector<ImplConstraint> impl_constraints_;
|
||||
std::vector<IntrinsicConstraint> intrinsic_constraints_;
|
||||
std::vector<EqualityConstraint> equality_constraints_;
|
||||
std::vector<RewriteConstraint> rewrite_constraints_;
|
||||
std::vector<LookupContext> lookup_contexts_;
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// 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
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: result: 5
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
class A {
|
||||
var n: i32;
|
||||
fn Get[self: Self]() -> i32 { return self.n; }
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
return ({.n = 5} as A).Get();
|
||||
}
|
||||
@@ -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
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: 1
|
||||
// CHECK:STDOUT: 2
|
||||
// CHECK:STDOUT: 3
|
||||
// CHECK:STDOUT: result: 0
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
base class A {
|
||||
var a: i32;
|
||||
}
|
||||
|
||||
base class B extends A {
|
||||
var b: i32;
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
var c: i32;
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
var c: C = {.base = {.base = {.a = 1}, .b = 2}, .c = 3};
|
||||
let (pa: A*, pb: B*, pc: C*) = (&c, &c, &c);
|
||||
Print("{0}", pa->a);
|
||||
Print("{0}", pb->b);
|
||||
Print("{0}", pc->c);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: result: 3
|
||||
|
||||
package Foo api;
|
||||
|
||||
class X {
|
||||
fn F[self: Self](o: Self) -> Self { return {.n = self.n + o.n}; }
|
||||
var n: i32;
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
return {.n = 1}.(X.F)({.n = 2}).n;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// 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
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: result: 4
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
fn F[T:! ImplicitAs(type)](x: T) -> type { return x; }
|
||||
|
||||
fn Main() -> i32 {
|
||||
var v: (i32, i32) as type = (1, 2);
|
||||
var w: F((i32, i32)) = (3, 4);
|
||||
v = w;
|
||||
return v[1];
|
||||
}
|
||||
Reference in New Issue
Block a user