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)(); +}