Skip to content

Commit

Permalink
Compound member access syntax. (#1233)
Browse files Browse the repository at this point in the history
Implement initial support for `A.(B)` syntax, per #989. Specifically, this supports:

* `object.(Type.member)` for instance members,
* `object.(Interface.member)` for instance and non-instance members,
* `object.(Type.(Interface.member))` for instance members,
* `Type.(Interface.member)` for non-instance members.

Three new AST nodes are introduced:

* `CompoundFieldAccessExpression` represents the `A.(B)` syntax.
* `MemberName` is a `Value` that represents the result of evaluating an expression such as `Type.member` or `Interface.member` or `Type.(Interface.member)`.
* `TypeOfMemberName` is the type of a `MemberName` value.

In order to handle members of classes and interfaces which have corresponding declarations and may need substitution into their types, and members of structs which don't have declarations but also don't need substitution, a class `Member` is introduced that can refer to either of these kinds of member.

Co-authored-by: Geoff Romer <[email protected]>
Co-authored-by: Jon Meow <[email protected]>
  • Loading branch information
3 people authored May 13, 2022
1 parent cb29efa commit 3d30f3c
Show file tree
Hide file tree
Showing 28 changed files with 939 additions and 40 deletions.
6 changes: 6 additions & 0 deletions common/fuzzing/carbon.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ message FieldAccessExpression {
optional Expression aggregate = 2;
}

message CompoundFieldAccessExpression {
optional Expression object = 1;
optional Expression path = 2;
}

message IndexExpression {
optional Expression aggregate = 1;
optional Expression offset = 2;
Expand Down Expand Up @@ -138,6 +143,7 @@ message Expression {
TypeTypeLiteral type_type_literal = 19;
UnimplementedExpression unimplemented_expression = 20;
ArrayTypeLiteral array_type_literal = 21;
CompoundFieldAccessExpression compound_field_access = 22;
}
}

Expand Down
9 changes: 9 additions & 0 deletions common/fuzzing/proto_to_carbon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ static auto ExpressionToCarbon(const Fuzzing::Expression& expression,
break;
}

case Fuzzing::Expression::kCompoundFieldAccess: {
const auto& field_access = expression.compound_field_access();
ExpressionToCarbon(field_access.object(), out);
out << ".(";
ExpressionToCarbon(field_access.path(), out);
out << ")";
break;
}

case Fuzzing::Expression::kIndex: {
const auto& index = expression.index();
ExpressionToCarbon(index.aggregate(), out);
Expand Down
1 change: 1 addition & 0 deletions explorer/ast/ast_rtti.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ abstract class Expression : AstNode;
class CallExpression : Expression;
class FunctionTypeLiteral : Expression;
class FieldAccessExpression : Expression;
class CompoundFieldAccessExpression : Expression;
class IndexExpression : Expression;
class IntTypeLiteral : Expression;
class ContinuationTypeLiteral : Expression;
Expand Down
6 changes: 6 additions & 0 deletions explorer/ast/expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ void Expression::Print(llvm::raw_ostream& out) const {
out << access.aggregate() << "." << access.field();
break;
}
case ExpressionKind::CompoundFieldAccessExpression: {
const auto& access = cast<CompoundFieldAccessExpression>(*this);
out << access.object() << ".(" << access.path() << ")";
break;
}
case ExpressionKind::TupleLiteral: {
out << "(";
llvm::ListSeparator sep;
Expand Down Expand Up @@ -233,6 +238,7 @@ void Expression::PrintID(llvm::raw_ostream& out) const {
break;
case ExpressionKind::IndexExpression:
case ExpressionKind::FieldAccessExpression:
case ExpressionKind::CompoundFieldAccessExpression:
case ExpressionKind::IfExpression:
case ExpressionKind::TupleLiteral:
case ExpressionKind::StructLiteral:
Expand Down
64 changes: 64 additions & 0 deletions explorer/ast/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
namespace Carbon {

class Value;
class MemberName;
class VariableType;
class ImplBinding;

Expand Down Expand Up @@ -181,6 +182,69 @@ class FieldAccessExpression : public Expression {
std::optional<Nonnull<const ImplBinding*>> impl_;
};

// A compound member access expression of the form `object.(path)`.
//
// `path` is required to have `TypeOfMemberName` type, and describes the member
// being accessed, which is one of:
//
// - An instance member of a type: `object.(Type.member)`.
// - A non-instance member of an interface: `Type.(Interface.member)` or
// `object.(Interface.member)`.
// - An instance member of an interface: `object.(Interface.member)` or
// `object.(Type.(Interface.member))`.
//
// Note that the `path` is evaluated during type-checking, not at runtime, so
// the corresponding `member` is determined statically.
class CompoundFieldAccessExpression : public Expression {
public:
explicit CompoundFieldAccessExpression(SourceLocation source_loc,
Nonnull<Expression*> object,
Nonnull<Expression*> path)
: Expression(AstNodeKind::CompoundFieldAccessExpression, source_loc),
object_(object),
path_(path) {}

static auto classof(const AstNode* node) -> bool {
return InheritsFromCompoundFieldAccessExpression(node->kind());
}

auto object() const -> const Expression& { return *object_; }
auto object() -> Expression& { return *object_; }
auto path() const -> const Expression& { return *path_; }
auto path() -> Expression& { return *path_; }

// Returns the `MemberName` value that evaluation of the path produced.
// Should not be called before typechecking.
auto member() const -> const MemberName& {
CARBON_CHECK(member_.has_value());
return **member_;
}

// Can only be called once, during typechecking.
void set_member(Nonnull<const MemberName*> member) {
CARBON_CHECK(!member_.has_value());
member_ = member;
}

// Returns the expression to use to compute the witness table, if this
// expression names an interface member.
auto impl() const -> std::optional<Nonnull<const Expression*>> {
return impl_;
}

// Can only be called once, during typechecking.
void set_impl(Nonnull<const Expression*> impl) {
CARBON_CHECK(!impl_.has_value());
impl_ = impl;
}

private:
Nonnull<Expression*> object_;
Nonnull<Expression*> path_;
std::optional<Nonnull<const MemberName*>> member_;
std::optional<Nonnull<const Expression*>> impl_;
};

class IndexExpression : public Expression {
public:
explicit IndexExpression(SourceLocation source_loc,
Expand Down
12 changes: 12 additions & 0 deletions explorer/fuzzing/ast_to_proto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ static auto ExpressionToProto(const Expression& expression)
break;
}

case ExpressionKind::CompoundFieldAccessExpression: {
const auto& field_access =
cast<CompoundFieldAccessExpression>(expression);
auto* field_access_proto =
expression_proto.mutable_compound_field_access();
*field_access_proto->mutable_object() =
ExpressionToProto(field_access.object());
*field_access_proto->mutable_path() =
ExpressionToProto(field_access.path());
break;
}

case ExpressionKind::IndexExpression: {
const auto& index = cast<IndexExpression>(expression);
auto* index_proto = expression_proto.mutable_index();
Expand Down
123 changes: 105 additions & 18 deletions explorer/interpreter/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,22 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
return todo_.FinishAction(arena_->New<LValue>(field));
}
}
case ExpressionKind::CompoundFieldAccessExpression: {
const auto& access = cast<CompoundFieldAccessExpression>(exp);
if (act.pos() == 0) {
return todo_.Spawn(std::make_unique<LValAction>(&access.object()));
} else {
CARBON_CHECK(!access.member().interface().has_value())
<< "unexpected lvalue interface member";
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> val,
Convert(act.results()[0], *access.member().base_type(),
exp.source_loc()));
Address object = cast<LValue>(*val).address();
Address field = object.SubobjectAddress(access.member().name());
return todo_.FinishAction(arena_->New<LValue>(field));
}
}
case ExpressionKind::IndexExpression: {
if (act.pos() == 0) {
// { {e[i] :: C, E, F} :: S, H}
Expand Down Expand Up @@ -495,7 +511,9 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
case Value::Kind::TypeOfInterfaceType:
case Value::Kind::TypeOfChoiceType:
case Value::Kind::TypeOfParameterizedEntityName:
case Value::Kind::TypeOfMemberName:
case Value::Kind::StaticArrayType:
case Value::Kind::MemberName:
// TODO: add `CARBON_CHECK(TypeEqual(type, value->dynamic_type()))`, once
// we have Value::dynamic_type.
return value;
Expand Down Expand Up @@ -773,30 +791,99 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
case ExpressionKind::FieldAccessExpression: {
const auto& access = cast<FieldAccessExpression>(exp);
if (act.pos() == 0) {
// { { e.f :: C, E, F} :: S, H}
// -> { { e :: [].f :: C, E, F} :: S, H}
return todo_.Spawn(
std::make_unique<ExpressionAction>(&access.aggregate()));
} else {
// { { v :: [].f :: C, E, F} :: S, H}
// -> { { v_f :: C, E, F} : S, H}
std::optional<Nonnull<const Witness*>> witness = std::nullopt;
if (access.impl().has_value()) {
if (const auto* member_name_type =
dyn_cast<TypeOfMemberName>(&access.static_type())) {
// The result is a member name, such as in `Type.field_name`. Form a
// suitable member name value.
CARBON_CHECK(phase() == Phase::CompileTime)
<< "should not form MemberNames at runtime";
std::optional<const InterfaceType*> iface_result;
std::optional<const Value*> type_result;
if (auto* iface_type = dyn_cast<InterfaceType>(act.results()[0])) {
iface_result = iface_type;
} else {
type_result = act.results()[0];
if (access.impl().has_value()) {
iface_result =
cast<InterfaceType>(access.impl().value()->interface());
}
}
MemberName* member_name = arena_->New<MemberName>(
type_result, iface_result, member_name_type->member());
return todo_.FinishAction(member_name);
} else {
// The result is the value of the named field, such as in
// `value.field_name`. Extract the value within the given object.
std::optional<Nonnull<const Witness*>> witness;
if (access.impl().has_value()) {
CARBON_ASSIGN_OR_RETURN(
auto witness_addr,
todo_.ValueOfNode(*access.impl(), access.source_loc()));
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> witness_value,
heap_.Read(llvm::cast<LValue>(witness_addr)->address(),
access.source_loc()));
witness = cast<Witness>(witness_value);
}
FieldPath::Component field(access.field(), witness);
CARBON_ASSIGN_OR_RETURN(
auto witness_addr,
todo_.ValueOfNode(*access.impl(), access.source_loc()));
Nonnull<const Value*> member,
act.results()[0]->GetField(arena_, FieldPath(field),
exp.source_loc()));
return todo_.FinishAction(member);
}
}
}
case ExpressionKind::CompoundFieldAccessExpression: {
const auto& access = cast<CompoundFieldAccessExpression>(exp);
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()));
} else if (act.pos() == 1 && access.impl().has_value() &&
!forming_member_name) {
// Next, if we're accessing an interface member, evaluate the `impl`
// expression to find the corresponding witness.
return todo_.Spawn(
std::make_unique<ExpressionAction>(access.impl().value()));
} else {
// Finally, produce the result.
if (forming_member_name) {
// If we're forming a member name, we must be in the outer evaluation
// in `Type.(Interface.method)`. Produce the same method name with
// its `type` field set.
CARBON_CHECK(phase() == Phase::CompileTime)
<< "should not form MemberNames at runtime";
CARBON_CHECK(!access.member().base_type().has_value())
<< "compound member access forming a member name should be "
"performing impl lookup";
auto* member_name = arena_->New<MemberName>(
act.results()[0], access.member().interface(),
access.member().member());
return todo_.FinishAction(member_name);
} else {
// Access the object to find the named member.
Nonnull<const Value*> object = act.results()[0];
std::optional<Nonnull<const Witness*>> witness;
if (access.impl().has_value()) {
witness = cast<Witness>(act.results()[1]);
} else {
CARBON_CHECK(access.member().base_type().has_value())
<< "compound access should have base type or impl";
CARBON_ASSIGN_OR_RETURN(
object, Convert(object, *access.member().base_type(),
exp.source_loc()));
}
FieldPath::Component field(access.member().name(), witness);
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> witness_value,
heap_.Read(llvm::cast<LValue>(witness_addr)->address(),
access.source_loc()));
witness = cast<Witness>(witness_value);
Nonnull<const Value*> member,
object->GetField(arena_, FieldPath(field), exp.source_loc()));
return todo_.FinishAction(member);
}
FieldPath::Component field(access.field(), witness);
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> member,
act.results()[0]->GetField(arena_, FieldPath(field),
exp.source_loc()));
return todo_.FinishAction(member);
}
}
case ExpressionKind::IdentifierExpression: {
Expand Down
6 changes: 6 additions & 0 deletions explorer/interpreter/resolve_names.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ static auto ResolveNames(Expression& expression,
ResolveNames(cast<FieldAccessExpression>(expression).aggregate(),
enclosing_scope));
break;
case ExpressionKind::CompoundFieldAccessExpression: {
auto& access = cast<CompoundFieldAccessExpression>(expression);
CARBON_RETURN_IF_ERROR(ResolveNames(access.object(), enclosing_scope));
CARBON_RETURN_IF_ERROR(ResolveNames(access.path(), enclosing_scope));
break;
}
case ExpressionKind::IndexExpression: {
auto& index = cast<IndexExpression>(expression);
CARBON_RETURN_IF_ERROR(ResolveNames(index.aggregate(), enclosing_scope));
Expand Down
Loading

0 comments on commit 3d30f3c

Please sign in to comment.