Evaluate the type prior to the . in Type.member accesses (#1337)

Per the design of member access, evaluate the first operand of `.` if it's a type in order to find which type it is, and perform the lookup there.

This allows us to handle the case where the first operand is of type `Type` rather than a more specific type, but can still be evaluated to some specific type value while type-checking.
This commit is contained in:
Richard Smith
2022-06-17 16:04:24 -07:00
committed by GitHub
parent 17ee3ed9b7
commit d1c8496858
2 changed files with 187 additions and 110 deletions
+109 -110
View File
@@ -1446,36 +1446,6 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
<< "struct " << struct_type << " does not have a field named "
<< access.member_name();
}
case Value::Kind::TypeType: {
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> type,
InterpExp(&access.object(), arena_, trace_stream_));
if (const auto* struct_type = dyn_cast<StructType>(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<TypeOfMemberName>(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<NominalClassType>(object_type);
if (std::optional<Nonnull<const Declaration*>> member = FindMember(
@@ -1521,77 +1491,6 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
<< " does not have a field named " << access.member_name();
}
}
case Value::Kind::TypeOfChoiceType: {
const ChoiceType& choice =
cast<TypeOfChoiceType>(object_type).choice_type();
std::optional<Nonnull<const Value*>> 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<const Value*> type = arena_->New<FunctionType>(
*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>(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<TypeOfClassType>(object_type).class_type();
if (std::optional<Nonnull<const Declaration*>> 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<FunctionDeclaration>(*member);
if (func->is_method()) {
break;
}
Nonnull<const Value*> 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<TypeOfMemberName>(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<TypeOfInterfaceType>(object_type)) {
type = &cast<TypeOfInterfaceType>(object_type).interface_type();
} else {
type = &cast<TypeOfConstraintType>(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<TypeOfMemberName>(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<Expression*> 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<const Value*> type,
InterpExp(&access.object(), arena_, trace_stream_));
@@ -1666,6 +1569,102 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> 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<const Value*> type,
InterpExp(&access.object(), arena_, trace_stream_));
switch (type->kind()) {
case Value::Kind::StructType: {
for (const auto& field : cast<StructType>(type)->fields()) {
if (access.member_name() == field.name) {
access.set_member(Member(&field));
access.set_static_type(
arena_->New<TypeOfMemberName>(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<ChoiceType>(*type);
std::optional<Nonnull<const Value*>> 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<const Value*> type =
arena_->New<FunctionType>(*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>(
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<NominalClassType>(*type);
if (std::optional<Nonnull<const Declaration*>> 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<FunctionDeclaration>(*member);
if (func->is_method()) {
break;
}
Nonnull<const Value*> 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<TypeOfMemberName>(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<TypeOfMemberName>(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<AlternativePattern>(*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<const Value*> type,
InterpExp(&alternative.choice_type(), arena_, trace_stream_));
if (!isa<ChoiceType>(type)) {
return CompilationError(alternative.source_loc())
<< "alternative pattern does not name a choice type.";
}
const ChoiceType& choice_type =
cast<TypeOfChoiceType>(alternative.choice_type().static_type())
.choice_type();
const ChoiceType& choice_type = cast<ChoiceType>(*type);
if (expected) {
CARBON_RETURN_IF_ERROR(ExpectType(alternative.source_loc(),
"alternative pattern", &choice_type,
@@ -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;
}