Skip to content

Commit

Permalink
Make addr me: Self* work for interface methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
zygoloid committed Nov 5, 2022
1 parent 9e8caca commit 3b2c53b
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 38 deletions.
19 changes: 8 additions & 11 deletions explorer/ast/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@ class MemberAccessExpression : public Expression {
// Can only be called once, during typechecking.
void set_is_type_access(bool type_access) { is_type_access_ = type_access; }

// Returns true if the member is a method that has a "me" declaration in an
// AddrPattern.
auto is_addr_me_method() const -> bool { return is_addr_me_method_; }

// Can only be called once, during typechecking.
void set_is_addr_me_method() { is_addr_me_method_ = true; }

// If `object` has a generic type, returns the witness value, which might be
// either concrete or symbolic. Otherwise, returns `std::nullopt`. Should not
// be called before typechecking.
Expand Down Expand Up @@ -292,6 +299,7 @@ class MemberAccessExpression : public Expression {
private:
Nonnull<Expression*> object_;
bool is_type_access_ = false;
bool is_addr_me_method_ = false;
std::optional<Nonnull<const Witness*>> impl_;
std::optional<Nonnull<const Value*>> constant_value_;
};
Expand Down Expand Up @@ -324,16 +332,6 @@ class SimpleMemberAccessExpression : public MemberAccessExpression {
member_ = member;
}

// Returns true if the field is a method that has a "me" declaration in an
// AddrPattern.
// TODO: Should be in MemberAccessExpression.
auto is_field_addr_me_method() const -> bool {
return is_field_addr_me_method_;
}

// Can only be called once, during typechecking.
void set_is_field_addr_me_method() { is_field_addr_me_method_ = true; }

// If `object` is a constrained type parameter and `member` was found in an
// interface, returns that interface. Should not be called before
// typechecking.
Expand All @@ -351,7 +349,6 @@ class SimpleMemberAccessExpression : public MemberAccessExpression {
private:
std::string member_name_;
std::optional<Member> member_;
bool is_field_addr_me_method_ = false;
std::optional<Nonnull<const InterfaceType*>> found_in_interface_;
};

Expand Down
10 changes: 7 additions & 3 deletions explorer/interpreter/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,7 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
bool forming_member_name = isa<TypeOfMemberName>(&access.static_type());
if (act.pos() == 0) {
// First, evaluate the first operand.
if (access.is_field_addr_me_method()) {
if (access.is_addr_me_method()) {
return todo_.Spawn(std::make_unique<LValAction>(&access.object()));
} else {
return todo_.Spawn(
Expand Down Expand Up @@ -1119,8 +1119,12 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
bool forming_member_name = isa<TypeOfMemberName>(&access.static_type());
if (act.pos() == 0) {
// First, evaluate the first operand.
return todo_.Spawn(
std::make_unique<ExpressionAction>(&access.object()));
if (access.is_addr_me_method()) {
return todo_.Spawn(std::make_unique<LValAction>(&access.object()));
} else {
return todo_.Spawn(
std::make_unique<ExpressionAction>(&access.object()));
}
} else if (act.pos() == 1 && access.impl().has_value() &&
!forming_member_name) {
// Next, if we're accessing an interface member, evaluate the `impl`
Expand Down
60 changes: 40 additions & 20 deletions explorer/interpreter/type_checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2430,6 +2430,27 @@ static auto IsInstanceMember(Member member) {
}
}

auto TypeChecker::CheckAddrMeAccess(
Nonnull<MemberAccessExpression*> access,
Nonnull<const FunctionDeclaration*> func_decl, const Bindings& bindings,
const ImplScope& impl_scope) -> ErrorOr<Success> {
if (func_decl->is_method() &&
func_decl->me_pattern().kind() == PatternKind::AddrPattern) {
access->set_is_addr_me_method();
Nonnull<const Value*> me_type =
Substitute(bindings, &func_decl->me_pattern().static_type());
CARBON_RETURN_IF_ERROR(
ExpectExactType(access->source_loc(), "method access, receiver type",
me_type, &access->object().static_type(), impl_scope));
if (access->object().value_category() != ValueCategory::Var) {
return ProgramError(access->source_loc())
<< "method " << *access
<< " requires its receiver to be an lvalue";
}
}
return Success();
}

auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
const ImplScope& impl_scope)
-> ErrorOr<Success> {
Expand Down Expand Up @@ -2568,21 +2589,8 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
break;
case DeclarationKind::FunctionDeclaration: {
const auto* func_decl = cast<FunctionDeclaration>(member);
if (func_decl->is_method() && func_decl->me_pattern().kind() ==
PatternKind::AddrPattern) {
access.set_is_field_addr_me_method();
Nonnull<const Value*> me_type =
Substitute(t_class.bindings(),
&func_decl->me_pattern().static_type());
CARBON_RETURN_IF_ERROR(ExpectType(
e->source_loc(), "method access, receiver type", me_type,
&access.object().static_type(), impl_scope));
if (access.object().value_category() != ValueCategory::Var) {
return ProgramError(e->source_loc())
<< "method " << access.member_name()
<< " requires its receiver to be an lvalue";
}
}
CARBON_RETURN_IF_ERROR(CheckAddrMeAccess(
&access, func_decl, t_class.bindings(), impl_scope));
access.set_value_category(ValueCategory::Let);
break;
}
Expand Down Expand Up @@ -2650,6 +2658,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
access.set_is_type_access(!IsInstanceMember(access.member()));
access.set_static_type(inst_member_type);

if (auto* func_decl = dyn_cast<FunctionDeclaration>(result.member)) {
CARBON_RETURN_IF_ERROR(
CheckAddrMeAccess(&access, func_decl, bindings, impl_scope));
}

// TODO: This is just a ConstraintImplWitness into the
// iface_constraint. If we can compute the right index, we can avoid
// re-resolving it.
Expand Down Expand Up @@ -2904,19 +2917,23 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
access.set_impl(impl);
}

auto substitute_into_member_type = [&]() {
Nonnull<const Value*> member_type = &member_name.member().type();
auto bindings_for_member = [&]() -> Bindings {
if (member_name.interface()) {
Nonnull<const InterfaceType*> iface_type = *member_name.interface();
Bindings bindings = iface_type->bindings();
bindings.Add(iface_type->declaration().self(), *base_type, witness);
return Substitute(bindings, member_type);
return bindings;
}
if (const auto* class_type =
dyn_cast<NominalClassType>(base_type.value())) {
return Substitute(class_type->bindings(), member_type);
return class_type->bindings();
}
return member_type;
return Bindings();
};

auto substitute_into_member_type = [&]() {
Nonnull<const Value*> member_type = &member_name.member().type();
return Substitute(bindings_for_member(), member_type);
};

switch (std::optional<Nonnull<const Declaration*>> decl =
Expand All @@ -2939,6 +2956,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
<< "vacuous compound member access";
access.set_static_type(substitute_into_member_type());
access.set_value_category(ValueCategory::Let);
CARBON_RETURN_IF_ERROR(
CheckAddrMeAccess(&access, cast<FunctionDeclaration>(*decl),
bindings_for_member(), impl_scope));
return Success();
}
break;
Expand Down
8 changes: 8 additions & 0 deletions explorer/interpreter/type_checker.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ class TypeChecker {
Nonnull<const Declaration*> member;
};

// Checks a member access that might be accessing a function taking `addr me:
// Self*`. If it does, this function marks the member access accordingly and
// ensures the object argument is an lvalue.
auto CheckAddrMeAccess(Nonnull<MemberAccessExpression*> access,
Nonnull<const FunctionDeclaration*> func_decl,
const Bindings& bindings, const ImplScope& impl_scope)
-> ErrorOr<Success>;

// Traverses the AST rooted at `e`, populating the static_type() of all nodes
// and ensuring they follow Carbon's typing rules.
//
Expand Down
2 changes: 1 addition & 1 deletion explorer/interpreter/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
mem_decl.has_value()) {
const auto& fun_decl = cast<FunctionDeclaration>(**mem_decl);
if (fun_decl.is_method()) {
return arena->New<BoundMethodValue>(&fun_decl, v,
return arena->New<BoundMethodValue>(&fun_decl, me_value,
&impl_witness->bindings());
} else {
// Class function.
Expand Down
2 changes: 1 addition & 1 deletion explorer/testdata/addr/fail_method_let.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Point {

fn Main() -> i32 {
let p: Point = Point.Origin();
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/addr/fail_method_let.carbon:[[@LINE+1]]: method GetSetX requires its receiver to be an lvalue
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/addr/fail_method_let.carbon:[[@LINE+1]]: method p.GetSetX requires its receiver to be an lvalue
var x: auto = p.GetSetX(42);
if (p.x == 42) {
return x;
Expand Down
4 changes: 3 additions & 1 deletion explorer/testdata/addr/fail_method_me_type.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ class Point {

fn Main() -> i32 {
var p: Point = Point.Origin();
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/addr/fail_method_me_type.carbon:[[@LINE+1]]: type error in method access, receiver type: 'class Point' is not implicitly convertible to 'class Shape'
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/addr/fail_method_me_type.carbon:[[@LINE+3]]: type error in method access, receiver type
// CHECK:STDERR: expected: class Shape
// CHECK:STDERR: actual: class Point
var x: auto = p.GetSetX(42);
if (p.x == 42) {
return x;
Expand Down
1 change: 0 additions & 1 deletion explorer/testdata/destructor/dont_call_in_method.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// AUTOUPDATE: %{explorer} %s
// CHECK:STDOUT: TEST
// CHECK:STDOUT: TEST 2
// CHECK:STDOUT: DESTRUCTOR A 1
Expand Down
50 changes: 50 additions & 0 deletions explorer/testdata/interface/addr_me.carbon
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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: 75
// CHECK:STDOUT: 100
// CHECK:STDOUT: result: 0

package ExplorerTest api;

interface Vector {
fn Zero() -> Self;
fn Add[addr me: Self*](b: Self);
fn Scale[addr me: Self*](v: i32);
}

class Point {
var x: i32;
var y: i32;
impl as Vector {
fn Zero() -> Self {
return {.x = 1, .y = 1};
}
fn Add[addr me: Self*](b: Self) {
(*me).x = (*me).x + b.x;
(*me).y = (*me).y + b.y;
}
fn Scale[addr me: Self*](v: i32) {
(*me).x = (*me).x * v;
(*me).y = (*me).y * v;
}
}
}

fn AddAndScaleGeneric[T:! Vector](p: T*, s: i32) {
(*p).Add(T.Zero());
(*p).(Vector.Scale)(s);
(*p).(T.(Vector.Scale))(s);
}

fn Main() -> i32 {
var a: Point = {.x = 2, .y = 3};
AddAndScaleGeneric(&a, 5);
Print("{0}", a.x);
Print("{0}", a.y);
return 0;
}

0 comments on commit 3b2c53b

Please sign in to comment.