diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 704ebd398877..84821da9a849 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -1446,36 +1446,6 @@ auto TypeChecker::TypeCheckExp(Nonnull e, << "struct " << struct_type << " does not have a field named " << access.member_name(); } - case Value::Kind::TypeType: { - CARBON_ASSIGN_OR_RETURN( - Nonnull type, - InterpExp(&access.object(), arena_, trace_stream_)); - if (const auto* struct_type = dyn_cast(type)) { - for (const auto& field : struct_type->fields()) { - if (access.member_name() == field.name) { - access.set_member(Member(&field)); - 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.member_name(); - } - // TODO: 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(object_type); if (std::optional> member = FindMember( @@ -1521,77 +1491,6 @@ auto TypeChecker::TypeCheckExp(Nonnull e, << " does not have a field named " << access.member_name(); } } - case Value::Kind::TypeOfChoiceType: { - const ChoiceType& choice = - cast(object_type).choice_type(); - std::optional> parameter_types = - choice.FindAlternative(access.member_name()); - if (!parameter_types.has_value()) { - return CompilationError(e->source_loc()) - << "choice " << choice.name() - << " does not have an alternative named " - << access.member_name(); - } - Nonnull type = arena_->New( - *parameter_types, llvm::None, &choice, llvm::None, llvm::None); - // TODO: Should there be a Declaration corresponding to each choice - // type alternative? - access.set_member(Member( - arena_->New(NamedValue{access.member_name(), type}))); - access.set_static_type(type); - access.set_value_category(ValueCategory::Let); - return Success(); - } - case Value::Kind::TypeOfClassType: { - const NominalClassType& class_type = - cast(object_type).class_type(); - if (std::optional> member = FindMember( - access.member_name(), class_type.declaration().members()); - member.has_value()) { - access.set_member(Member(member.value())); - switch ((*member)->kind()) { - case DeclarationKind::FunctionDeclaration: { - const auto& func = cast(*member); - if (func->is_method()) { - break; - } - Nonnull field_type = Substitute( - class_type.type_args(), &(*member)->static_type()); - access.set_static_type(field_type); - access.set_value_category(ValueCategory::Let); - return Success(); - } - default: - break; - } - 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 member named " - << access.member_name(); - } - } - case Value::Kind::TypeOfInterfaceType: - case Value::Kind::TypeOfConstraintType: { - const Value* type; - if (isa(object_type)) { - type = &cast(object_type).interface_type(); - } else { - type = &cast(object_type).constraint_type(); - } - CARBON_ASSIGN_OR_RETURN( - ConstraintLookupResult result, - LookupInConstraint(e->source_loc(), type, access.member_name())); - access.set_member(Member(result.member)); - access.set_found_in_interface(result.interface); - access.set_static_type( - arena_->New(Member(result.member))); - access.set_value_category(ValueCategory::Let); - return Success(); - } case Value::Kind::VariableType: { // This case handles access to a method on a receiver whose type // is a type variable. For example, `x.foo` where the type of @@ -1622,10 +1521,14 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } case Value::Kind::InterfaceType: case Value::Kind::ConstraintType: { - // This case handles access to a class function from a type variable. - // If `T` is a type variable and `foo` is a class function in an - // interface implemented by `T`, then `T.foo` accesses the `foo` class - // function of `T`. + // This case handles access to a class function from a constrained + // type variable. If `T` is a type variable and `foo` is a class + // function in an interface implemented by `T`, then `T.foo` accesses + // the `foo` class function of `T`. + // + // TODO: Per the language rules, we are supposed to also perform + // lookup into `type` and report an ambiguity if the name is found in + // both places. CARBON_ASSIGN_OR_RETURN( Nonnull type, InterpExp(&access.object(), arena_, trace_stream_)); @@ -1666,6 +1569,102 @@ auto TypeChecker::TypeCheckExp(Nonnull e, access.set_value_category(ValueCategory::Let); return Success(); } + case Value::Kind::TypeType: + case Value::Kind::TypeOfChoiceType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfConstraintType: + case Value::Kind::TypeOfInterfaceType: { + // This is member access into an unconstrained type. Evaluate it and + // perform lookup in the result. + CARBON_ASSIGN_OR_RETURN( + Nonnull type, + InterpExp(&access.object(), arena_, trace_stream_)); + switch (type->kind()) { + case Value::Kind::StructType: { + for (const auto& field : cast(type)->fields()) { + if (access.member_name() == field.name) { + access.set_member(Member(&field)); + access.set_static_type( + arena_->New(Member(&field))); + access.set_value_category(ValueCategory::Let); + return Success(); + } + } + return CompilationError(access.source_loc()) + << "struct " << *type << " does not have a field named " + << " does not have a field named " << access.member_name(); + } + case Value::Kind::ChoiceType: { + const ChoiceType& choice = cast(*type); + std::optional> parameter_types = + choice.FindAlternative(access.member_name()); + if (!parameter_types.has_value()) { + return CompilationError(e->source_loc()) + << "choice " << choice.name() + << " does not have an alternative named " + << access.member_name(); + } + Nonnull type = + arena_->New(*parameter_types, llvm::None, + &choice, llvm::None, llvm::None); + // TODO: Should there be a Declaration corresponding to each + // choice type alternative? + access.set_member(Member(arena_->New( + NamedValue{access.member_name(), type}))); + access.set_static_type(type); + access.set_value_category(ValueCategory::Let); + return Success(); + } + case Value::Kind::NominalClassType: { + const NominalClassType& class_type = + cast(*type); + if (std::optional> member = + FindMember(access.member_name(), + class_type.declaration().members()); + member.has_value()) { + access.set_member(Member(member.value())); + switch ((*member)->kind()) { + case DeclarationKind::FunctionDeclaration: { + const auto& func = cast(*member); + if (func->is_method()) { + break; + } + Nonnull field_type = Substitute( + class_type.type_args(), &(*member)->static_type()); + access.set_static_type(field_type); + access.set_value_category(ValueCategory::Let); + return Success(); + } + default: + break; + } + 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 member named " + << access.member_name(); + } + } + case Value::Kind::InterfaceType: + case Value::Kind::ConstraintType: { + CARBON_ASSIGN_OR_RETURN(ConstraintLookupResult result, + LookupInConstraint(e->source_loc(), type, + access.member_name())); + access.set_member(Member(result.member)); + access.set_found_in_interface(result.interface); + access.set_static_type( + arena_->New(Member(result.member))); + access.set_value_category(ValueCategory::Let); + return Success(); + } + default: + return CompilationError(access.source_loc()) + << "unsupported member access into type " << *type; + } + } default: return CompilationError(e->source_loc()) << "member access, unexpected " << object_type << " in " << *e; @@ -2426,14 +2425,14 @@ auto TypeChecker::TypeCheckPattern( auto& alternative = cast(*p); CARBON_RETURN_IF_ERROR( TypeCheckExp(&alternative.choice_type(), impl_scope)); - if (alternative.choice_type().static_type().kind() != - Value::Kind::TypeOfChoiceType) { + CARBON_ASSIGN_OR_RETURN( + Nonnull type, + InterpExp(&alternative.choice_type(), arena_, trace_stream_)); + if (!isa(type)) { return CompilationError(alternative.source_loc()) << "alternative pattern does not name a choice type."; } - const ChoiceType& choice_type = - cast(alternative.choice_type().static_type()) - .choice_type(); + const ChoiceType& choice_type = cast(*type); if (expected) { CARBON_RETURN_IF_ERROR(ExpectType(alternative.source_loc(), "alternative pattern", &choice_type, diff --git a/explorer/testdata/member_access/evaluate_type_before_dot.carbon b/explorer/testdata/member_access/evaluate_type_before_dot.carbon new file mode 100644 index 000000000000..09277dd28425 --- /dev/null +++ b/explorer/testdata/member_access/evaluate_type_before_dot.carbon @@ -0,0 +1,78 @@ +// 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: Struct OK +// CHECK: Choice OK +// CHECK: Class OK +// CHECK: Interface OK +// CHECK: Constraint OK +// CHECK: result: 0 + +package Foo api; + +choice Choice { Alternative() } +class Class { fn F(n: i32) -> i32 { return n + 1; } } +interface Interface { fn G[me: Self]() -> Self; } +interface AnotherInterface {} + +impl i32 as Interface { fn G[me: i32]() -> i32 { return me + 1; } } +impl i32 as AnotherInterface {} + +// TODO: These are intended to be called at compile time. Mark them as +// constexpr once we have syntax for that. +fn GetStruct() -> Type { return {.n: i32}; } +fn GetChoice() -> Type { return Choice; } +fn GetClass() -> Type { return Class; } +fn GetInterface() -> Type { return Interface; } +fn GetConstraint() -> Type { return Interface & AnotherInterface; } + +fn TestStruct() { + var s: GetStruct() = {.n = 1}; + if (s.(GetStruct().n) == 1) { + Print("Struct OK\n"); + } +} + +fn TestChoice() { + var c: GetChoice() = GetChoice().Alternative(); + match (c) { + case GetChoice().Alternative() => { + Print("Choice OK\n"); + } + } +} + +fn TestClass() { + if (GetClass().F(1) == 2) { + Print("Class OK\n"); + } +} + +fn TestInterface() { + var n: i32 = 1; + if (n.(GetInterface().G)() == 2) { + Print("Interface OK\n"); + } +} + +fn TestConstraint() { + var n: i32 = 1; + if (n.(GetConstraint().G)() == 2) { + Print("Constraint OK\n"); + } +} + +fn Main() -> i32 { + TestStruct(); + TestChoice(); + TestClass(); + TestInterface(); + TestConstraint(); + return 0; +}