mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user