From 235cb88a8e775b27495baabfd1f95f4971ecf905 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 13 May 2022 16:52:41 -0700 Subject: [PATCH] Compound member access syntax. (#1233) 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 Co-authored-by: Jon Meow --- common/fuzzing/carbon.proto | 6 + common/fuzzing/proto_to_carbon.cpp | 9 + explorer/ast/ast_rtti.txt | 1 + explorer/ast/expression.cpp | 6 + explorer/ast/expression.h | 64 +++++ explorer/fuzzing/ast_to_proto.cpp | 12 + explorer/interpreter/interpreter.cpp | 123 ++++++-- explorer/interpreter/resolve_names.cpp | 6 + explorer/interpreter/type_checker.cpp | 272 ++++++++++++++++-- explorer/interpreter/type_checker.h | 4 + explorer/interpreter/value.cpp | 52 +++- explorer/interpreter/value.h | 95 ++++++ explorer/syntax/parser.ypp | 5 + .../testdata/class/fail_method_deduced.carbon | 25 ++ .../class/fail_method_from_class.carbon | 2 +- .../testdata/class/fail_method_in_var.carbon | 24 ++ .../testdata/class/fail_return_method.carbon | 24 ++ .../fail_qualified_non_member.carbon | 19 ++ .../member_access/fail_vacuous_access.carbon | 22 ++ .../fail_vacuous_access_via_type_param.carbon | 25 ++ ...rly_vacuous_access_with_impl_lookup.carbon | 22 ++ ...acuous_access_with_instance_binding.carbon | 22 ++ .../param_qualified_interface_member.carbon | 29 ++ .../qualified_class_member.carbon | 21 ++ .../qualified_interface_member.carbon | 26 ++ .../qualified_param_member.carbon | 29 ++ .../qualified_struct_member.carbon | 15 + .../type_qualified_interface_member.carbon | 21 ++ 28 files changed, 940 insertions(+), 41 deletions(-) create mode 100644 explorer/testdata/class/fail_method_deduced.carbon create mode 100644 explorer/testdata/class/fail_method_in_var.carbon create mode 100644 explorer/testdata/class/fail_return_method.carbon create mode 100644 explorer/testdata/member_access/fail_qualified_non_member.carbon create mode 100644 explorer/testdata/member_access/fail_vacuous_access.carbon create mode 100644 explorer/testdata/member_access/fail_vacuous_access_via_type_param.carbon create mode 100644 explorer/testdata/member_access/nearly_vacuous_access_with_impl_lookup.carbon create mode 100644 explorer/testdata/member_access/nearly_vacuous_access_with_instance_binding.carbon create mode 100644 explorer/testdata/member_access/param_qualified_interface_member.carbon create mode 100644 explorer/testdata/member_access/qualified_class_member.carbon create mode 100644 explorer/testdata/member_access/qualified_interface_member.carbon create mode 100644 explorer/testdata/member_access/qualified_param_member.carbon create mode 100644 explorer/testdata/member_access/qualified_struct_member.carbon create mode 100644 explorer/testdata/member_access/type_qualified_interface_member.carbon diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index f0de0539a409..123423e27dd2 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -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; @@ -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; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 4aeb2bbb6a51..f9e0f3db7927 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -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); diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index dc5d67873373..a56801a550cf 100644 --- a/explorer/ast/ast_rtti.txt +++ b/explorer/ast/ast_rtti.txt @@ -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; diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index 3eb3c43eb814..aff7443e8906 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -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(*this); + out << access.object() << ".(" << access.path() << ")"; + break; + } case ExpressionKind::TupleLiteral: { out << "("; llvm::ListSeparator sep; @@ -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: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index ab023be7cda4..90841c93094b 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -24,6 +24,7 @@ namespace Carbon { class Value; +class MemberName; class VariableType; class ImplBinding; @@ -181,6 +182,69 @@ class FieldAccessExpression : public Expression { std::optional> 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 object, + Nonnull 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 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> { + return impl_; + } + + // Can only be called once, during typechecking. + void set_impl(Nonnull impl) { + CARBON_CHECK(!impl_.has_value()); + impl_ = impl; + } + + private: + Nonnull object_; + Nonnull path_; + std::optional> member_; + std::optional> impl_; +}; + class IndexExpression : public Expression { public: explicit IndexExpression(SourceLocation source_loc, diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 7a6a919cf997..bea126644786 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -111,6 +111,18 @@ static auto ExpressionToProto(const Expression& expression) break; } + case ExpressionKind::CompoundFieldAccessExpression: { + const auto& field_access = + cast(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(expression); auto* index_proto = expression_proto.mutable_index(); diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index ffb2dbc4838f..d3c43bca5e67 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -337,6 +337,22 @@ auto Interpreter::StepLvalue() -> ErrorOr { return todo_.FinishAction(arena_->New(field)); } } + case ExpressionKind::CompoundFieldAccessExpression: { + const auto& access = cast(exp); + if (act.pos() == 0) { + return todo_.Spawn(std::make_unique(&access.object())); + } else { + CARBON_CHECK(!access.member().interface().has_value()) + << "unexpected lvalue interface member"; + CARBON_ASSIGN_OR_RETURN( + Nonnull val, + Convert(act.results()[0], *access.member().base_type(), + exp.source_loc())); + Address object = cast(*val).address(); + Address field = object.SubobjectAddress(access.member().name()); + return todo_.FinishAction(arena_->New(field)); + } + } case ExpressionKind::IndexExpression: { if (act.pos() == 0) { // { {e[i] :: C, E, F} :: S, H} @@ -495,7 +511,9 @@ auto Interpreter::Convert(Nonnull 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; @@ -773,30 +791,99 @@ auto Interpreter::StepExp() -> ErrorOr { case ExpressionKind::FieldAccessExpression: { const auto& access = cast(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(&access.aggregate())); } else { - // { { v :: [].f :: C, E, F} :: S, H} - // -> { { v_f :: C, E, F} : S, H} - std::optional> witness = std::nullopt; - if (access.impl().has_value()) { + if (const auto* member_name_type = + dyn_cast(&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 iface_result; + std::optional type_result; + if (auto* iface_type = dyn_cast(act.results()[0])) { + iface_result = iface_type; + } else { + type_result = act.results()[0]; + if (access.impl().has_value()) { + iface_result = + cast(access.impl().value()->interface()); + } + } + MemberName* member_name = arena_->New( + 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> 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 witness_value, + heap_.Read(llvm::cast(witness_addr)->address(), + access.source_loc())); + witness = cast(witness_value); + } + FieldPath::Component field(access.field(), witness); CARBON_ASSIGN_OR_RETURN( - auto witness_addr, - todo_.ValueOfNode(*access.impl(), access.source_loc())); + Nonnull member, + act.results()[0]->GetField(arena_, FieldPath(field), + exp.source_loc())); + return todo_.FinishAction(member); + } + } + } + case ExpressionKind::CompoundFieldAccessExpression: { + const auto& access = cast(exp); + bool forming_member_name = isa(&access.static_type()); + if (act.pos() == 0) { + // First, evaluate the first operand. + return todo_.Spawn( + std::make_unique(&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(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( + act.results()[0], access.member().interface(), + access.member().member()); + return todo_.FinishAction(member_name); + } else { + // Access the object to find the named member. + Nonnull object = act.results()[0]; + std::optional> witness; + if (access.impl().has_value()) { + witness = cast(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 witness_value, - heap_.Read(llvm::cast(witness_addr)->address(), - access.source_loc())); - witness = cast(witness_value); + Nonnull member, + object->GetField(arena_, FieldPath(field), exp.source_loc())); + return todo_.FinishAction(member); } - FieldPath::Component field(access.field(), witness); - CARBON_ASSIGN_OR_RETURN( - Nonnull member, - act.results()[0]->GetField(arena_, FieldPath(field), - exp.source_loc())); - return todo_.FinishAction(member); } } case ExpressionKind::IdentifierExpression: { diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 6f6c719c62c1..02753ba08d37 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -111,6 +111,12 @@ static auto ResolveNames(Expression& expression, ResolveNames(cast(expression).aggregate(), enclosing_scope)); break; + case ExpressionKind::CompoundFieldAccessExpression: { + auto& access = cast(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(expression); CARBON_RETURN_IF_ERROR(ResolveNames(index.aggregate(), enclosing_scope)); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index db10ad4eb0b9..8523f3c5a72a 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -61,6 +61,57 @@ static auto ExpectPointerType(SourceLocation source_loc, return Success(); } +// Returns whether the value is a type whose values are themselves known to be +// types. +static auto IsTypeOfType(Nonnull value) -> bool { + switch (value->kind()) { + case Value::Kind::IntValue: + case Value::Kind::FunctionValue: + case Value::Kind::BoundMethodValue: + case Value::Kind::PointerValue: + case Value::Kind::LValue: + case Value::Kind::BoolValue: + case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: + case Value::Kind::AlternativeValue: + case Value::Kind::BindingPlaceholderValue: + case Value::Kind::AlternativeConstructorValue: + case Value::Kind::ContinuationValue: + case Value::Kind::StringValue: + case Value::Kind::Witness: + case Value::Kind::ParameterizedEntityName: + case Value::Kind::MemberName: + case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: + // These are values, not types. + return false; + case Value::Kind::IntType: + case Value::Kind::BoolType: + case Value::Kind::FunctionType: + case Value::Kind::PointerType: + case Value::Kind::StructType: + case Value::Kind::NominalClassType: + case Value::Kind::ChoiceType: + case Value::Kind::ContinuationType: + case Value::Kind::StringType: + case Value::Kind::StaticArrayType: + case Value::Kind::TupleValue: + // These are types whose values are not types. + return false; + case Value::Kind::AutoType: + case Value::Kind::VariableType: + // A value of one of these types could be a type, but isn't known to be. + return false; + case Value::Kind::TypeType: + case Value::Kind::InterfaceType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfChoiceType: + // A value of one of these types is itself always a type. + return true; + } +} + // Returns whether the value is a valid result from a type expression, // as opposed to a non-type value. static auto IsType(Nonnull value) -> bool { @@ -80,6 +131,12 @@ static auto IsType(Nonnull value) -> bool { case Value::Kind::StringValue: case Value::Kind::Witness: case Value::Kind::ParameterizedEntityName: + case Value::Kind::MemberName: + return false; + case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: + // Names aren't first-class values, and their types aren't first-class + // types. return false; case Value::Kind::IntType: case Value::Kind::BoolType: @@ -96,7 +153,6 @@ static auto IsType(Nonnull value) -> bool { case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: - case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::StaticArrayType: case Value::Kind::AutoType: return true; @@ -141,6 +197,9 @@ static auto IsConcreteType(Nonnull value) -> bool { case Value::Kind::StringValue: case Value::Kind::Witness: case Value::Kind::ParameterizedEntityName: + case Value::Kind::MemberName: + case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: return false; case Value::Kind::IntType: case Value::Kind::BoolType: @@ -157,7 +216,6 @@ static auto IsConcreteType(Nonnull value) -> bool { case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: - case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::StaticArrayType: return true; case Value::Kind::AutoType: @@ -509,10 +567,12 @@ auto TypeChecker::ArgumentDeduction( case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: return handle_non_deduced_type(); // The rest of these cases should never happen. case Value::Kind::Witness: case Value::Kind::ParameterizedEntityName: + case Value::Kind::MemberName: case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: @@ -562,6 +622,8 @@ auto TypeChecker::Substitute( const auto& fn_type = cast(*type); auto param = Substitute(dict, &fn_type.parameters()); auto ret = Substitute(dict, &fn_type.return_type()); + // FIXME: Only remove the bindings that are in `dict`; we may still need + // to do deduction. return arena_->New(param, llvm::None, ret, llvm::None, llvm::None); } @@ -609,10 +671,12 @@ auto TypeChecker::Substitute( case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: 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: case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: @@ -760,8 +824,10 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } case ExpressionKind::TupleLiteral: { std::vector> arg_types; - for (auto& arg : cast(*e).fields()) { + for (auto* arg : cast(*e).fields()) { CARBON_RETURN_IF_ERROR(TypeCheckExp(arg, impl_scope)); + CARBON_RETURN_IF_ERROR( + ExpectIsConcreteType(arg->source_loc(), &arg->static_type())); arg_types.push_back(&arg->static_type()); } e->set_static_type(arena_->New(std::move(arg_types))); @@ -772,6 +838,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, std::vector arg_types; for (auto& arg : cast(*e).fields()) { CARBON_RETURN_IF_ERROR(TypeCheckExp(&arg.expression(), impl_scope)); + CARBON_RETURN_IF_ERROR(ExpectIsConcreteType( + arg.expression().source_loc(), &arg.expression().static_type())); arg_types.push_back({arg.name(), &arg.expression().static_type()}); } e->set_static_type(arena_->New(std::move(arg_types))); @@ -817,6 +885,35 @@ auto TypeChecker::TypeCheckExp(Nonnull e, << "struct " << struct_type << " does not have a field named " << access.field(); } + case Value::Kind::TypeType: { + CARBON_ASSIGN_OR_RETURN( + Nonnull type, + InterpExp(&access.aggregate(), arena_, trace_stream_)); + if (const auto* struct_type = dyn_cast(type)) { + for (const auto& field : struct_type->fields()) { + if (access.field() == field.name) { + access.set_static_type( + arena_->New(Member(&field))); + access.set_value_category(ValueCategory::Let); + return Success(); + } + } + return CompilationError(access.source_loc()) + << "struct " << *struct_type + << " does not have a field named " << access.field(); + } + // FIXME: We should handle all types here, not only structs. For + // example: + // fn Main() -> i32 { + // class Class { var n: i32; }; + // let T:! Type = Class; + // let x: T = {.n = 0}; + // return x.(T.n); + // } + // is valid, and the type of `T` here is `Type`, not `typeof(Class)`. + return CompilationError(access.source_loc()) + << "unsupported member access into type " << *type; + } case Value::Kind::NominalClassType: { const auto& t_class = cast(aggregate_type); if (std::optional> member = @@ -881,11 +978,29 @@ auto TypeChecker::TypeCheckExp(Nonnull e, default: break; } - return CompilationError(access.source_loc()) - << access.field() << " is not a class function"; + access.set_static_type( + arena_->New(Member(*member))); + access.set_value_category(ValueCategory::Let); + return Success(); } else { return CompilationError(access.source_loc()) - << class_type << " does not have a class function named " + << class_type << " does not have a member named " + << access.field(); + } + } + case Value::Kind::TypeOfInterfaceType: { + const InterfaceType& iface_type = + cast(aggregate_type).interface_type(); + if (std::optional> member = FindMember( + access.field(), iface_type.declaration().members()); + member.has_value()) { + access.set_static_type( + arena_->New(Member(*member))); + access.set_value_category(ValueCategory::Let); + return Success(); + } else { + return CompilationError(access.source_loc()) + << iface_type << " does not have a member named " << access.field(); } } @@ -940,14 +1055,33 @@ auto TypeChecker::TypeCheckExp(Nonnull e, if (std::optional> member = FindMember(access.field(), iface_decl.members()); member.has_value()) { - const Value& member_type = (*member)->static_type(); - BindingMap binding_map = iface_type.args(); - binding_map[iface_decl.self()] = &var_type; - Nonnull inst_member_type = - Substitute(binding_map, &member_type); - access.set_static_type(inst_member_type); CARBON_CHECK(var_type.binding().impl_binding().has_value()); access.set_impl(*var_type.binding().impl_binding()); + + switch ((*member)->kind()) { + case DeclarationKind::FunctionDeclaration: { + const auto& func = cast(*member); + if (func->is_method()) { + break; + } + const Value& member_type = (*member)->static_type(); + BindingMap binding_map = iface_type.args(); + binding_map[iface_decl.self()] = &var_type; + Nonnull inst_member_type = + Substitute(binding_map, &member_type); + access.set_static_type(inst_member_type); + return Success(); + } + default: + break; + } + // FIXME: Consider setting the static type of all interface member + // declarations and instance member declarations to be member name + // types, rather than special-casing member accesses that name + // them. + access.set_static_type( + arena_->New(Member(*member))); + access.set_value_category(ValueCategory::Let); return Success(); } else { return CompilationError(e->source_loc()) @@ -962,6 +1096,106 @@ auto TypeChecker::TypeCheckExp(Nonnull e, << *e; } } + case ExpressionKind::CompoundFieldAccessExpression: { + auto& access = cast(*e); + CARBON_RETURN_IF_ERROR(TypeCheckExp(&access.object(), impl_scope)); + CARBON_RETURN_IF_ERROR(TypeCheckExp(&access.path(), impl_scope)); + if (!isa(access.path().static_type())) { + return CompilationError(e->source_loc()) + << "expected name of instance member or interface member in " + "compound member access, found " + << access.path().static_type(); + } + + // Evaluate the member name expression to determine which member we're + // accessing. + CARBON_ASSIGN_OR_RETURN(Nonnull member_name_value, + InterpExp(&access.path(), arena_, trace_stream_)); + const auto& member_name = cast(*member_name_value); + access.set_member(&member_name); + + bool has_instance = true; + std::optional> base_type = member_name.base_type(); + if (!base_type.has_value()) { + if (IsTypeOfType(&access.object().static_type())) { + // This is `Type.(member_name)`, where `member_name` doesn't specify + // a type. This access doesn't perform instance binding. + CARBON_ASSIGN_OR_RETURN( + base_type, InterpExp(&access.object(), arena_, trace_stream_)); + has_instance = false; + } else { + // This is `value.(member_name)`, where `member_name` doesn't specify + // a type. The member will be found in the type of `value`, or in a + // corresponding `impl` if `member_name` is an interface member. + base_type = &access.object().static_type(); + } + } 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())); + } + + // Perform impl selection if necessary. + if (std::optional> iface = + member_name.interface()) { + CARBON_ASSIGN_OR_RETURN( + Nonnull impl, + impl_scope.Resolve(*iface, *base_type, e->source_loc(), *this)); + access.set_impl(impl); + } + + auto SubstituteIntoMemberType = [&]() { + Nonnull member_type = &member_name.member().type(); + if (member_name.interface()) { + Nonnull iface_type = *member_name.interface(); + BindingMap binding_map = iface_type->args(); + binding_map[iface_type->declaration().self()] = *base_type; + return Substitute(binding_map, member_type); + } + if (auto* class_type = dyn_cast(base_type.value())) { + return Substitute(class_type->type_args(), member_type); + } + return member_type; + }; + + switch (std::optional> decl = + member_name.member().declaration(); + decl ? decl.value()->kind() + : DeclarationKind::VariableDeclaration) { + case DeclarationKind::VariableDeclaration: + if (has_instance) { + access.set_static_type(SubstituteIntoMemberType()); + access.set_value_category(access.object().value_category()); + return Success(); + } + break; + case DeclarationKind::FunctionDeclaration: { + bool is_method = cast(*decl.value()).is_method(); + if (has_instance || !is_method) { + // This should not be possible: the name of a static member + // function should have function type not member name type. + CARBON_CHECK(!has_instance || is_method || + !member_name.base_type().has_value()) + << "vacuous compound member access"; + access.set_static_type(SubstituteIntoMemberType()); + access.set_value_category(ValueCategory::Let); + return Success(); + } + break; + } + default: + CARBON_FATAL() << "member " << member_name + << " is not a field or method"; + break; + } + + access.set_static_type( + arena_->New(member_name.member())); + access.set_value_category(ValueCategory::Let); + return Success(); + } case ExpressionKind::IdentifierExpression: { auto& ident = cast(*e); if (ident.value_node().base().kind() == @@ -1367,14 +1601,20 @@ void TypeChecker::BringImplsIntoScope( } } -void TypeChecker::BringImplIntoScope(Nonnull impl_binding, - ImplScope& impl_scope) { - CARBON_CHECK(impl_binding->type_var()->symbolic_identity().has_value()); +auto TypeChecker::CreateImplReference(Nonnull impl_binding) + -> Nonnull { auto impl_id = arena_->New(impl_binding->source_loc(), "impl"); impl_id->set_value_node(impl_binding); + return impl_id; +} + +void TypeChecker::BringImplIntoScope(Nonnull impl_binding, + ImplScope& impl_scope) { + CARBON_CHECK(impl_binding->type_var()->symbolic_identity().has_value()); impl_scope.Add(impl_binding->interface(), - *impl_binding->type_var()->symbolic_identity(), impl_id); + *impl_binding->type_var()->symbolic_identity(), + CreateImplReference(impl_binding)); } auto TypeChecker::TypeCheckPattern( diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 3e09d29afef8..9c959bd956bf 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -122,6 +122,10 @@ class TypeChecker { void BringPatternImplsIntoScope(Nonnull p, ImplScope& impl_scope); + // Create a reference to the given `impl` binding. + auto CreateImplReference(Nonnull impl_binding) + -> Nonnull; + // Add the given ImplBinding to the given `impl_scope`. void BringImplIntoScope(Nonnull impl_binding, ImplScope& impl_scope); diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index 21b583757b8e..06e43f5894bd 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -354,6 +354,25 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::ParameterizedEntityName: out << *GetName(cast(*this).declaration()); break; + case Value::Kind::MemberName: { + const auto& member_name = cast(*this); + if (member_name.base_type().has_value()) { + out << *member_name.base_type().value(); + } + if (member_name.base_type().has_value() && + member_name.interface().has_value()) { + out << "("; + } + if (member_name.interface().has_value()) { + out << *member_name.interface().value(); + } + out << "." << member_name.name(); + if (member_name.base_type().has_value() && + member_name.interface().has_value()) { + out << ")"; + } + break; + } case Value::Kind::ChoiceType: out << "choice " << cast(*this).name(); break; @@ -388,9 +407,13 @@ void Value::Print(llvm::raw_ostream& out) const { << ")"; break; case Value::Kind::TypeOfParameterizedEntityName: - out << "typeof(" << cast(*this).name() - << ")"; + out << "parameterized entity name " + << cast(*this).name(); break; + case Value::Kind::TypeOfMemberName: { + out << "member name " << cast(*this).member().name(); + break; + } case Value::Kind::StaticArrayType: { const auto& array_type = cast(*this); out << "[" << array_type.element_type() << "; " << array_type.size() @@ -522,10 +545,6 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::TypeOfChoiceType: return TypeEqual(&cast(*t1).choice_type(), &cast(*t2).choice_type()); - case Value::Kind::TypeOfParameterizedEntityName: { - return ValueEqual(&cast(*t1).name(), - &cast(*t2).name()); - } case Value::Kind::StaticArrayType: { const auto& array1 = cast(*t1); const auto& array2 = cast(*t2); @@ -546,6 +565,9 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::BindingPlaceholderValue: case Value::Kind::ContinuationValue: case Value::Kind::ParameterizedEntityName: + case Value::Kind::MemberName: + case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: CARBON_FATAL() << "TypeEqual used to compare non-type values\n" << *t1 << "\n" << *t2; @@ -645,6 +667,7 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: case Value::Kind::StaticArrayType: return TypeEqual(v1, v2); case Value::Kind::NominalClassValue: @@ -654,6 +677,7 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::ContinuationValue: case Value::Kind::PointerValue: case Value::Kind::LValue: + case Value::Kind::MemberName: // TODO: support pointer comparisons once we have a clearer distinction // between pointers and lvalues. CARBON_FATAL() << "ValueEqual does not support this kind of value: " @@ -703,6 +727,22 @@ auto FindMember(const std::string& name, return std::nullopt; } +auto Member::name() const -> std::string { + if (const Declaration* decl = member_.dyn_cast()) { + return GetName(*decl).value(); + } else { + return member_.get()->name; + } +} + +auto Member::type() const -> const Value& { + if (const Declaration* decl = member_.dyn_cast()) { + return decl->static_type(); + } else { + return *member_.get()->value; + } +} + void ImplBinding::Print(llvm::raw_ostream& out) const { out << "impl binding " << *type_var_ << " as " << *iface_; } diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index a449059dfe6c..11051cf5cc03 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -17,6 +17,7 @@ #include "explorer/interpreter/address.h" #include "explorer/interpreter/field_path.h" #include "explorer/interpreter/stack.h" +#include "llvm/ADT/PointerUnion.h" #include "llvm/Support/Compiler.h" namespace Carbon { @@ -58,6 +59,7 @@ class Value { ContinuationType, // The type of a continuation. VariableType, // e.g., generic type parameters. ParameterizedEntityName, + MemberName, BindingPlaceholderValue, AlternativeConstructorValue, ContinuationValue, // A first-class continuation value. @@ -67,6 +69,7 @@ class Value { TypeOfInterfaceType, TypeOfChoiceType, TypeOfParameterizedEntityName, + TypeOfMemberName, StaticArrayType, }; @@ -757,6 +760,73 @@ class ParameterizedEntityName : public Value { Nonnull params_; }; +// A member of a type. +// +// This is either a declared member of a class, interface, or similar, or a +// member of a struct with no declaration. +class Member { + public: + explicit Member(const Declaration* declaration) : member_(declaration) {} + explicit Member(const NamedValue* struct_member) : member_(struct_member) {} + + // The name of the member. + auto name() const -> std::string; + // The declared type of the member, which might include type variables. + auto type() const -> const Value&; + // A declaration of the member, if any exists. + auto declaration() const -> std::optional> { + if (const Declaration* decl = member_.dyn_cast()) { + return decl; + } + return std::nullopt; + } + + private: + llvm::PointerUnion, Nonnull> + member_; +}; + +// The name of a member of a class or interface. +// +// These values are used to represent the second operand of a compound member +// access expression: `x.(A.B)`, and can also be the value of an alias +// declaration, but cannot be used in most other contexts. +class MemberName : public Value { + public: + MemberName(std::optional> base_type, + std::optional> interface, + Member member) + : Value(Kind::MemberName), + base_type_(base_type), + interface_(interface), + member_(member) { + CARBON_CHECK(base_type || interface) + << "member name must be in a type, an interface, or both"; + } + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::MemberName; + } + + // The type for which `name` is a member or a member of an `impl`. + auto base_type() const -> std::optional> { + return base_type_; + } + // The interface for which `name` is a member, if any. + auto interface() const -> std::optional> { + return interface_; + } + // The member. + auto member() const -> Member { return member_; } + // The name of the member. + auto name() const -> std::string { return member().name(); } + + private: + std::optional> base_type_; + std::optional> interface_; + Member member_; +}; + // A first-class continuation representation of a fragment of the stack. // A continuation value behaves like a pointer to the underlying stack // fragment, which is exposed by `Stack()`. @@ -909,6 +979,31 @@ class TypeOfParameterizedEntityName : public Value { Nonnull name_; }; +// The type of a member name expression. +// +// This is used for member names that don't denote a specific object or value +// until used on the right-hand side of a `.`, such as an instance method or +// field name, or any member function in an interface. +// +// Such expressions can appear only as the target of an `alias` declaration or +// as the member name in a compound member access. +class TypeOfMemberName : public Value { + public: + explicit TypeOfMemberName(Member member) + : Value(Kind::TypeOfMemberName), member_(member) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::TypeOfMemberName; + } + + // TODO: consider removing this or moving it elsewhere in the AST, + // since it's arguably part of the expression value rather than its type. + auto member() const -> Member { return member_; } + + private: + Member member_; +}; + // The type of a statically-sized array. // // Note that values of this type are represented as tuples. diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 8095c0c05a3f..84ab2685670b 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -326,6 +326,11 @@ postfix_expression: primary_expression | postfix_expression designator { $$ = arena->New(context.source_loc(), $1, $2); } +| postfix_expression PERIOD LEFT_PARENTHESIS expression RIGHT_PARENTHESIS + { + $$ = arena->New(context.source_loc(), $1, + $4); + } | postfix_expression LEFT_SQUARE_BRACKET expression RIGHT_SQUARE_BRACKET { $$ = arena->New(context.source_loc(), $1, $3); } | intrinsic_identifier tuple diff --git a/explorer/testdata/class/fail_method_deduced.carbon b/explorer/testdata/class/fail_method_deduced.carbon new file mode 100644 index 000000000000..f011182006db --- /dev/null +++ b/explorer/testdata/class/fail_method_deduced.carbon @@ -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: %{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 C { + fn F() {} + fn G[me: Self]() {} +} + +fn H[T:! Type](x: T) {} + +fn Main() -> i32 { + H(C.F); + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_deduced.carbon:[[@LINE+1]]: Expected a type, but got member name G + H(C.G); + return 0; +} diff --git a/explorer/testdata/class/fail_method_from_class.carbon b/explorer/testdata/class/fail_method_from_class.carbon index 027e0b61cabd..414747b9f08e 100644 --- a/explorer/testdata/class/fail_method_from_class.carbon +++ b/explorer/testdata/class/fail_method_from_class.carbon @@ -24,6 +24,6 @@ class Point { } fn Main() -> i32 { - // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_from_class.carbon:[[@LINE+1]]: GetX is not a class function + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_from_class.carbon:[[@LINE+1]]: in call `Point.GetX()`, expected callee to be a function, found `member name GetX` return Point.GetX(); } diff --git a/explorer/testdata/class/fail_method_in_var.carbon b/explorer/testdata/class/fail_method_in_var.carbon new file mode 100644 index 000000000000..58cfdfd42bbf --- /dev/null +++ b/explorer/testdata/class/fail_method_in_var.carbon @@ -0,0 +1,24 @@ +// 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 C { + fn F() {} + fn G[me: Self]() {} +} + +fn Main() -> i32 { + var f: auto = C.F; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_in_var.carbon:[[@LINE+1]]: Expected a type, but got member name G + var g: auto = C.G; + + return 0; +} diff --git a/explorer/testdata/class/fail_return_method.carbon b/explorer/testdata/class/fail_return_method.carbon new file mode 100644 index 000000000000..ef6e05ecf013 --- /dev/null +++ b/explorer/testdata/class/fail_return_method.carbon @@ -0,0 +1,24 @@ +// 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 C { + fn F() {} + fn G[me: Self]() {} +} + +fn ReturnF() -> auto { return C.F; } +// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_return_method.carbon:[[@LINE+1]]: Expected a type, but got member name G +fn ReturnG() -> auto { return C.G; } + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/member_access/fail_qualified_non_member.carbon b/explorer/testdata/member_access/fail_qualified_non_member.carbon new file mode 100644 index 000000000000..6252ce58ebe3 --- /dev/null +++ b/explorer/testdata/member_access/fail_qualified_non_member.carbon @@ -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 +// +// 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 Foo api; +fn F[me: i32]() {} +fn Main() -> i32 { + // TODO: It's unclear whether this is valid per the current rules. See + // https://github.com/carbon-language/carbon-lang/pull/1122 + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/member_access/fail_qualified_non_member.carbon:[[@LINE+1]]: expected name of instance member or interface member in compound member access, found fn () -> () + 42.(F)(); + return 0; +} diff --git a/explorer/testdata/member_access/fail_vacuous_access.carbon b/explorer/testdata/member_access/fail_vacuous_access.carbon new file mode 100644 index 000000000000..140c1782dc88 --- /dev/null +++ b/explorer/testdata/member_access/fail_vacuous_access.carbon @@ -0,0 +1,22 @@ +// 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 Foo api; +interface A { fn F() -> i32; } +class X { + impl as A { + fn F() -> i32 { return 1; } + } +} +fn Main() -> i32 { + var a: X = {}; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/member_access/fail_vacuous_access.carbon:[[@LINE+1]]: expected name of instance member or interface member in compound member access, found fn () -> i32 + return a.(X.(A.F))(); +} diff --git a/explorer/testdata/member_access/fail_vacuous_access_via_type_param.carbon b/explorer/testdata/member_access/fail_vacuous_access_via_type_param.carbon new file mode 100644 index 000000000000..a47469912491 --- /dev/null +++ b/explorer/testdata/member_access/fail_vacuous_access_via_type_param.carbon @@ -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: %{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 Foo api; +interface A { fn F() -> i32; } +class X { + impl as A { + fn F() -> i32 { return 1; } + } +} +fn F[T:! A](a: T) -> i32 { + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/member_access/fail_vacuous_access_via_type_param.carbon:[[@LINE+1]]: expected name of instance member or interface member in compound member access, found fn () -> i32 + return a.(T.F)(); +} +fn Main() -> i32 { + var a: X = {}; + return F(a); +} diff --git a/explorer/testdata/member_access/nearly_vacuous_access_with_impl_lookup.carbon b/explorer/testdata/member_access/nearly_vacuous_access_with_impl_lookup.carbon new file mode 100644 index 000000000000..fe6c1d6d053e --- /dev/null +++ b/explorer/testdata/member_access/nearly_vacuous_access_with_impl_lookup.carbon @@ -0,0 +1,22 @@ +// 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 Foo api; +interface A { fn F() -> i32; } +class X { + impl as A { + fn F() -> i32 { return 1; } + } +} +fn Main() -> i32 { + var a: X = {}; + return a.(A.F)(); +} diff --git a/explorer/testdata/member_access/nearly_vacuous_access_with_instance_binding.carbon b/explorer/testdata/member_access/nearly_vacuous_access_with_instance_binding.carbon new file mode 100644 index 000000000000..9cc8cffd7871 --- /dev/null +++ b/explorer/testdata/member_access/nearly_vacuous_access_with_instance_binding.carbon @@ -0,0 +1,22 @@ +// 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 Foo api; +interface A { fn F[me: Self]() -> i32; } +class X { + impl as A { + fn F[me: Self]() -> i32 { return 1; } + } +} +fn Main() -> i32 { + var a: X = {}; + return a.(X.(A.F))(); +} diff --git a/explorer/testdata/member_access/param_qualified_interface_member.carbon b/explorer/testdata/member_access/param_qualified_interface_member.carbon new file mode 100644 index 000000000000..bfb8f63f810d --- /dev/null +++ b/explorer/testdata/member_access/param_qualified_interface_member.carbon @@ -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: 3 + +package Foo api; +interface A { + fn F[me: Self](o: Self) -> Self; +} +class X { + impl as A { + fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; } + } + var n: i32; +} +fn F[T:! A](v: T, w: T) -> T { + return v.(T.(A.F))(w); +} +fn Main() -> i32 { + var v: X = {.n = 1}; + var w: X = {.n = 2}; + return F(v, w).n; +} diff --git a/explorer/testdata/member_access/qualified_class_member.carbon b/explorer/testdata/member_access/qualified_class_member.carbon new file mode 100644 index 000000000000..c2e16ab4f13e --- /dev/null +++ b/explorer/testdata/member_access/qualified_class_member.carbon @@ -0,0 +1,21 @@ +// 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; +} +fn Main() -> i32 { + var v: X = {.n = 1}; + var w: X = {.n = 2}; + return v.(X.F)(w).(X.n); +} diff --git a/explorer/testdata/member_access/qualified_interface_member.carbon b/explorer/testdata/member_access/qualified_interface_member.carbon new file mode 100644 index 000000000000..77fca174a6f3 --- /dev/null +++ b/explorer/testdata/member_access/qualified_interface_member.carbon @@ -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 Foo api; +interface A { + fn F[me: Self](o: Self) -> Self; +} +class X { + impl as A { + fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; } + } + var n: i32; +} +fn Main() -> i32 { + var v: X = {.n = 1}; + var w: X = {.n = 2}; + return v.(A.F)(w).n; +} diff --git a/explorer/testdata/member_access/qualified_param_member.carbon b/explorer/testdata/member_access/qualified_param_member.carbon new file mode 100644 index 000000000000..e3d16d6f94b7 --- /dev/null +++ b/explorer/testdata/member_access/qualified_param_member.carbon @@ -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: 3 + +package Foo api; +interface A { + fn F[me: Self](o: Self) -> Self; +} +class X { + impl as A { + fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; } + } + var n: i32; +} +fn F[T:! A](v: T, w: T) -> T { + return v.(T.F)(w); +} +fn Main() -> i32 { + var v: X = {.n = 1}; + var w: X = {.n = 2}; + return F(v, w).n; +} diff --git a/explorer/testdata/member_access/qualified_struct_member.carbon b/explorer/testdata/member_access/qualified_struct_member.carbon new file mode 100644 index 000000000000..5a1076c176d7 --- /dev/null +++ b/explorer/testdata/member_access/qualified_struct_member.carbon @@ -0,0 +1,15 @@ +// 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: 2 + +package Foo api; +fn Main() -> i32 { + return {.m = 1, .n = 2}.({.n: i32, .m: i32}.n); +} diff --git a/explorer/testdata/member_access/type_qualified_interface_member.carbon b/explorer/testdata/member_access/type_qualified_interface_member.carbon new file mode 100644 index 000000000000..b95a0ef3f4d0 --- /dev/null +++ b/explorer/testdata/member_access/type_qualified_interface_member.carbon @@ -0,0 +1,21 @@ +// 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 Foo api; +interface HasDefault { + fn Default() -> Self; +} +impl i32 as HasDefault { + fn Default() -> i32 { return 42; } +} +fn Main() -> i32 { + return i32.(HasDefault.Default)(); +}