When performing name lookup, determine the generic instance within which the lookup result was found. (#4118)

Require types into which qualified lookup is performed to be completely
defined. Eventually this will trigger substitution into the definition
for generic types.
This commit is contained in:
Richard Smith
2024-07-10 18:35:55 +00:00
committed by GitHub
parent a4ef5dd591
commit 6d3c915bbf
20 changed files with 422 additions and 89 deletions
+67 -22
View File
@@ -263,7 +263,7 @@ auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
}
auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
SemIR::NameId name_id) -> SemIR::InstId {
SemIR::NameId name_id) -> LookupResult {
// TODO: Check for shadowed lookup results.
// Find the results from ancestor lexical scopes. These will be combined with
@@ -272,22 +272,33 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
scope_stack().LookupInLexicalScopes(name_id);
// Walk the non-lexical scopes and perform lookups into each of them.
for (auto [index, name_scope_id] : llvm::reverse(non_lexical_scopes)) {
if (auto non_lexical_result =
LookupQualifiedName(node_id, name_id, name_scope_id,
/*required=*/false);
non_lexical_result.is_valid()) {
for (auto [index, lookup_scope_id] : llvm::reverse(non_lexical_scopes)) {
// Enclosing non-lexical scopes cannot correspond to an instance of a
// generic, so it's always OK to pass an invalid generic instance here.
// Note that the lookup result might still be found in an extended scope, so
// it can be in a generic instance.
if (auto non_lexical_result = LookupQualifiedName(
node_id, name_id,
{.name_scope_id = lookup_scope_id,
.instance_id = SemIR::GenericInstanceId::Invalid},
/*required=*/false);
non_lexical_result.inst_id.is_valid()) {
return non_lexical_result;
}
}
if (lexical_result.is_valid()) {
return lexical_result;
// A lexical scope never needs an associated generic instance. If there's a
// lexically enclosing generic, then it also encloses the point of use of
// the name.
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = lexical_result};
}
// We didn't find anything at all.
DiagnoseNameNotFound(node_id, name_id);
return SemIR::InstId::BuiltinError;
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
}
// Handles lookup through the import_ir_scopes for LookupNameInExactScope.
@@ -391,15 +402,16 @@ auto Context::LookupNameInExactScope(SemIRLoc loc, SemIR::NameId name_id,
}
auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
SemIR::NameScopeId scope_id, bool required)
-> SemIR::InstId {
llvm::SmallVector<SemIR::NameScopeId> scope_ids = {scope_id};
auto result_id = SemIR::InstId::Invalid;
LookupScope scope, bool required)
-> LookupResult {
llvm::SmallVector<LookupScope> scopes = {scope};
LookupResult result = {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::Invalid};
bool has_error = false;
// Walk this scope and, if nothing is found here, the scopes it extends.
while (!scope_ids.empty()) {
auto scope_id = scope_ids.pop_back_val();
while (!scopes.empty()) {
auto [scope_id, instance_id] = scopes.pop_back_val();
const auto& scope = name_scopes().Get(scope_id);
has_error |= scope.has_error;
@@ -407,13 +419,19 @@ auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
LookupNameInExactScope(node_id, name_id, scope_id, scope);
if (!scope_result_id.is_valid()) {
// Nothing found in this scope: also look in its extended scopes.
auto extended = llvm::reverse(scope.extended_scopes);
scope_ids.append(extended.begin(), extended.end());
auto extended = scope.extended_scopes;
scopes.reserve(scopes.size() + extended.size());
for (auto extended_id : llvm::reverse(extended)) {
// TODO: Track a constant describing the extended scope, and substitute
// into it to determine its corresponding generic instance.
scopes.push_back({.name_scope_id = extended_id,
.instance_id = SemIR::GenericInstanceId::Invalid});
}
continue;
}
// If this is our second lookup result, diagnose an ambiguity.
if (result_id.is_valid()) {
if (result.inst_id.is_valid()) {
// TODO: This is currently not reachable because the only scope that can
// extend is a class scope, and it can only extend a single base class.
// Add test coverage once this is possible.
@@ -423,20 +441,23 @@ auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
SemIR::NameId);
emitter_->Emit(node_id, NameAmbiguousDueToExtend, name_id);
// TODO: Add notes pointing to the scopes.
return SemIR::InstId::BuiltinError;
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
}
result_id = scope_result_id;
result.inst_id = scope_result_id;
result.instance_id = instance_id;
}
if (required && !result_id.is_valid()) {
if (required && !result.inst_id.is_valid()) {
if (!has_error) {
DiagnoseNameNotFound(node_id, name_id);
}
return SemIR::InstId::BuiltinError;
return {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
}
return result_id;
return result;
}
// Returns the scope of the Core package, or Invalid if it's not found.
@@ -779,6 +800,7 @@ class TypeCompleter {
}
return false;
}
// TODO: Trigger generic resolution here for a generic class.
Push(class_info.object_repr_id);
break;
}
@@ -1042,6 +1064,29 @@ auto Context::TryToCompleteType(
return TypeCompleter(*this, diagnoser).Complete(type_id);
}
auto Context::TryToDefineType(
SemIR::TypeId type_id,
std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser)
-> bool {
if (!TryToCompleteType(type_id, diagnoser)) {
return false;
}
if (auto interface = types().TryGetAs<SemIR::InterfaceType>(type_id)) {
auto interface_id = interface->interface_id;
if (!interfaces().Get(interface_id).is_defined()) {
auto builder = (*diagnoser)();
NoteUndefinedInterface(interface_id, builder);
builder.Emit();
return false;
}
// TODO: Trigger generic resolution here for a generic instance.
}
return true;
}
auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id)
-> SemIR::TypeId {
CARBON_CHECK(constant_id.is_constant())
+32 -3
View File
@@ -26,6 +26,24 @@
namespace Carbon::Check {
// Information about a scope in which we can perform name lookup.
struct LookupScope {
// The name scope in which names are searched.
SemIR::NameScopeId name_scope_id;
// The generic instance for the name scope, or `Invalid` if the name scope is
// not an instance of a generic.
SemIR::GenericInstanceId instance_id;
};
// A result produced by name lookup.
struct LookupResult {
// The generic instance in which the lookup result was found. `Invalid` if the
// result was not found in a generic instance.
SemIR::GenericInstanceId instance_id;
// The declaration that was found by name lookup.
SemIR::InstId inst_id;
};
// Context and shared functionality for semantics handlers.
class Context {
public:
@@ -122,7 +140,7 @@ class Context {
// Performs an unqualified name lookup, returning the referenced instruction.
auto LookupUnqualifiedName(Parse::NodeId node_id, SemIR::NameId name_id)
-> SemIR::InstId;
-> LookupResult;
// Performs a name lookup in a specified scope, returning the referenced
// instruction. Does not look into extended scopes. Returns an invalid
@@ -134,8 +152,8 @@ class Context {
// Performs a qualified name lookup in a specified scope and in scopes that
// it extends, returning the referenced instruction.
auto LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
SemIR::NameScopeId scope_id, bool required = true)
-> SemIR::InstId;
LookupScope scope, bool required = true)
-> LookupResult;
// Returns the instruction corresponding to a name in the core package, or
// BuiltinError if not found.
@@ -239,6 +257,17 @@ class Context {
std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
std::nullopt) -> bool;
// Attempts to complete and define the type `type_id`. Returns `true` if the
// type is defined, or `false` if no definition is available. A defined type
// has known members.
//
// This is the same as `TryToCompleteType` except for interfaces, which are
// complete before they are fully defined.
auto TryToDefineType(
SemIR::TypeId type_id,
std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
std::nullopt) -> bool;
// Returns the type `type_id` as a complete type, or produces an incomplete
// type error and returns an error type. This is a convenience wrapper around
// TryToCompleteType.
+7
View File
@@ -281,6 +281,13 @@ auto GetConstantInInstance(Context& context,
return context.constant_values().Get(symbolic.inst_id);
}
auto GetConstantValueInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::InstId inst_id) -> SemIR::ConstantId {
return GetConstantInInstance(context, instance_id,
context.constant_values().Get(inst_id));
}
auto GetTypeInInstance(Context& context, SemIR::GenericInstanceId instance_id,
SemIR::TypeId type_id) -> SemIR::TypeId {
auto const_id = context.types().GetConstantId(type_id);
+9
View File
@@ -54,6 +54,15 @@ auto GetConstantInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::ConstantId const_id) -> SemIR::ConstantId;
// Gets the substituted constant value of an instruction within a specified
// instance of a generic. Note that this does not perform substitution, and will
// return `Invalid` if the substituted constant value is not yet known.
//
// TODO: Move this to sem_ir so that lowering can use it.
auto GetConstantValueInInstance(Context& context,
SemIR::GenericInstanceId instance_id,
SemIR::InstId inst_id) -> SemIR::ConstantId;
// Gets the substituted value of a type within a specified instance of a
// generic. Note that this does not perform substitution, and will return
// `Invalid` if the substituted type is not yet known.
+6 -6
View File
@@ -80,15 +80,15 @@ static auto GetIdentifierAsName(Context& context, Parse::NodeId node_id)
// lookup.
static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id,
SemIR::NameId name_id) -> bool {
auto value_id = context.LookupUnqualifiedName(node_id, name_id);
// TODO: Lookup should produce this.
auto instance_id = SemIR::GenericInstanceId::Invalid;
auto value = context.insts().Get(value_id);
auto type_id = GetTypeInInstance(context, instance_id, value.type_id());
auto result = context.LookupUnqualifiedName(node_id, name_id);
auto value = context.insts().Get(result.inst_id);
auto type_id =
GetTypeInInstance(context, result.instance_id, value.type_id());
CARBON_CHECK(type_id.is_valid()) << "Missing type for " << value;
context.AddInstAndPush<SemIR::NameRef>(
node_id, {.type_id = type_id, .name_id = name_id, .value_id = value_id});
node_id,
{.type_id = type_id, .name_id = name_id, .value_id = result.inst_id});
return true;
}
+50 -45
View File
@@ -8,54 +8,57 @@
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/subst.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
// Returns the name scope corresponding to base_id, or nullopt if not a scope.
// Returns the lookup scope corresponding to base_id, or nullopt if not a scope.
// On invalid scopes, prints a diagnostic and still returns the scope.
static auto GetAsNameScope(Context& context, Parse::NodeId node_id,
SemIR::ConstantId base_const_id)
-> std::optional<SemIR::NameScopeId> {
static auto GetAsLookupScope(Context& context, Parse::NodeId node_id,
SemIR::ConstantId base_const_id)
-> std::optional<LookupScope> {
auto base_id = context.constant_values().GetInstId(base_const_id);
auto base = context.insts().Get(base_id);
if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
return base_as_namespace->name_scope_id;
return LookupScope{.name_scope_id = base_as_namespace->name_scope_id,
.instance_id = SemIR::GenericInstanceId::Invalid};
}
// TODO: Consider refactoring the near-identical class and interface support
// below.
if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
context.TryToDefineType(
context.GetTypeIdForTypeConstant(base_const_id), [&] {
CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
"Member access into incomplete class `{0}`.",
std::string);
return context.emitter().Build(
node_id, QualifiedExprInIncompleteClassScope,
context.sem_ir().StringifyType(base_const_id));
});
auto& class_info = context.classes().Get(base_as_class->class_id);
if (!class_info.is_defined()) {
CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
"Member access into incomplete class `{0}`.",
std::string);
auto builder = context.emitter().Build(
node_id, QualifiedExprInIncompleteClassScope,
context.sem_ir().StringifyType(base_const_id));
context.NoteIncompleteClass(base_as_class->class_id, builder);
builder.Emit();
}
return class_info.scope_id;
return LookupScope{.name_scope_id = class_info.scope_id,
.instance_id = base_as_class->instance_id};
}
if (auto base_as_interface = base.TryAs<SemIR::InterfaceType>()) {
context.TryToDefineType(
context.GetTypeIdForTypeConstant(base_const_id), [&] {
CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
"Member access into undefined interface `{0}`.",
std::string);
return context.emitter().Build(
node_id, QualifiedExprInUndefinedInterfaceScope,
context.sem_ir().StringifyType(base_const_id));
});
auto& interface_info =
context.interfaces().Get(base_as_interface->interface_id);
if (!interface_info.is_defined()) {
CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
"Member access into undefined interface `{0}`.",
std::string);
auto builder = context.emitter().Build(
node_id, QualifiedExprInUndefinedInterfaceScope,
context.sem_ir().StringifyType(base_const_id));
context.NoteUndefinedInterface(base_as_interface->interface_id, builder);
builder.Emit();
}
return interface_info.scope_id;
return LookupScope{.name_scope_id = interface_info.scope_id,
.instance_id = base_as_interface->instance_id};
}
// TODO: Per the design, if `base_id` is any kind of type, then lookup should
// treat it as a name scope, even if it doesn't have members. For example,
@@ -96,11 +99,10 @@ static auto IsInstanceMethod(const SemIR::File& sem_ir,
return false;
}
// Returns whether `name_scope_id` is a scope for which impl lookup should be
// performed if we find an associated entity.
static auto ScopeNeedsImplLookup(Context& context,
SemIR::NameScopeId name_scope_id) -> bool {
auto [_, inst] = context.name_scopes().GetInstIfValid(name_scope_id);
// Returns whether `scope` is a scope for which impl lookup should be performed
// if we find an associated entity.
static auto ScopeNeedsImplLookup(Context& context, LookupScope scope) -> bool {
auto [_, inst] = context.name_scopes().GetInstIfValid(scope.name_scope_id);
if (!inst) {
return false;
}
@@ -214,17 +216,20 @@ static auto LookupMemberNameInScope(Context& context, Parse::NodeId node_id,
SemIR::InstId /*base_id*/,
SemIR::NameId name_id,
SemIR::ConstantId name_scope_const_id,
SemIR::NameScopeId name_scope_id)
-> SemIR::InstId {
auto inst_id = name_scope_id.is_valid() ? context.LookupQualifiedName(
node_id, name_id, name_scope_id)
: SemIR::InstId::BuiltinError;
auto inst = context.insts().Get(inst_id);
LookupScope lookup_scope) -> SemIR::InstId {
LookupResult result = {.instance_id = SemIR::GenericInstanceId::Invalid,
.inst_id = SemIR::InstId::BuiltinError};
if (lookup_scope.name_scope_id.is_valid()) {
result = context.LookupQualifiedName(node_id, name_id, lookup_scope);
}
auto inst = context.insts().Get(result.inst_id);
auto type_id = GetTypeInInstance(context, result.instance_id, inst.type_id());
// TODO: Use a different kind of instruction that also references the
// `base_id` so that `SemIR` consumers can find it.
auto member_id = context.AddInst<SemIR::NameRef>(
node_id,
{.type_id = inst.type_id(), .name_id = name_id, .value_id = inst_id});
{.type_id = type_id, .name_id = name_id, .value_id = result.inst_id});
// If member name lookup finds an associated entity name, and the scope is not
// a facet type, perform impl lookup.
@@ -234,7 +239,7 @@ static auto LookupMemberNameInScope(Context& context, Parse::NodeId node_id,
// impl member is not supposed to be treated as ambiguous.
if (auto assoc_type = context.types().TryGetAs<SemIR::AssociatedEntityType>(
inst.type_id())) {
if (ScopeNeedsImplLookup(context, name_scope_id)) {
if (ScopeNeedsImplLookup(context, lookup_scope)) {
member_id = PerformImplLookup(context, node_id, name_scope_const_id,
*assoc_type, member_id);
}
@@ -303,9 +308,9 @@ auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
// into that scope.
if (auto base_const_id = context.constant_values().Get(base_id);
base_const_id.is_constant()) {
if (auto name_scope_id = GetAsNameScope(context, node_id, base_const_id)) {
if (auto lookup_scope = GetAsLookupScope(context, node_id, base_const_id)) {
return LookupMemberNameInScope(context, node_id, base_id, name_id,
base_const_id, *name_scope_id);
base_const_id, *lookup_scope);
}
}
@@ -327,8 +332,8 @@ auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
auto base_type_const_id = context.types().GetConstantId(base_type_id);
// Find the scope corresponding to the base type.
auto name_scope_id = GetAsNameScope(context, node_id, base_type_const_id);
if (!name_scope_id) {
auto lookup_scope = GetAsLookupScope(context, node_id, base_type_const_id);
if (!lookup_scope) {
// The base type is not a name scope. Try some fallback options.
if (auto struct_type = context.insts().TryGetAs<SemIR::StructType>(
context.constant_values().GetInstId(base_type_const_id))) {
@@ -364,7 +369,7 @@ auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
// Perform lookup into the base type.
auto member_id = LookupMemberNameInScope(context, node_id, base_id, name_id,
base_type_const_id, *name_scope_id);
base_type_const_id, *lookup_scope);
// Perform instance binding if we found an instance member.
member_id = PerformInstanceBinding(context, node_id, base_id, member_id);
+13 -4
View File
@@ -6,6 +6,7 @@
#include "toolchain/check/call.h"
#include "toolchain/check/context.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/member_access.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
@@ -37,17 +38,25 @@ static auto GetOperatorOpFunction(Context& context, Parse::AnyExprId node_id,
return SemIR::InstId::Invalid;
}
// TODO: For a parameterized interface, find the corresponding generic
// instance.
LookupScope scope = {.name_scope_id = interface_scope_id,
.instance_id = SemIR::GenericInstanceId::Invalid};
// Lookup `Interface.Op`.
auto op_ident_id = context.identifiers().Add(op.op_name);
auto op_id = context.LookupQualifiedName(
node_id, SemIR::NameId::ForIdentifier(op_ident_id), interface_scope_id,
auto op_result = context.LookupQualifiedName(
node_id, SemIR::NameId::ForIdentifier(op_ident_id), scope,
/*required=*/false);
if (!op_id.is_valid()) {
if (!op_result.inst_id.is_valid()) {
return SemIR::InstId::Invalid;
}
// Look through import_refs and aliases.
op_id = context.constant_values().GetConstantInstId(op_id);
auto op_const_id =
GetConstantInInstance(context, op_result.instance_id,
context.constant_values().Get(op_result.inst_id));
auto op_id = context.constant_values().GetInstId(op_const_id);
// We expect it to be an associated function.
if (context.insts().Is<SemIR::AssociatedEntity>(op_id)) {
+3 -2
View File
@@ -41,7 +41,8 @@ fn Run() -> i32 {
// CHECK:STDOUT: %.3: type = struct_type {.k: i32} [template]
// CHECK:STDOUT: %Run.type: type = fn_type @Run [template]
// CHECK:STDOUT: %Run: %Run.type = struct_value () [template]
// CHECK:STDOUT: %.4: i32 = int_literal 4 [template]
// CHECK:STDOUT: %.4: type = ptr_type %.3 [template]
// CHECK:STDOUT: %.5: i32 = int_literal 4 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -136,7 +137,7 @@ fn Run() -> i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %Class.ref: type = name_ref Class, file.%Class.decl [template = constants.%Class]
// CHECK:STDOUT: %F.ref: %F.type = name_ref F, @Class.%F.decl [template = constants.%F]
// CHECK:STDOUT: %.loc26_18: i32 = int_literal 4 [template = constants.%.4]
// CHECK:STDOUT: %.loc26_18: i32 = int_literal 4 [template = constants.%.5]
// CHECK:STDOUT: %F.call: init i32 = call %F.ref(%.loc26_18)
// CHECK:STDOUT: %.loc26_20.1: i32 = value_of_initializer %F.call
// CHECK:STDOUT: %.loc26_20.2: i32 = converted %F.call, %.loc26_20.1
@@ -29,6 +29,8 @@ let b: B = C.base;
// CHECK:STDOUT: %.3: type = ptr_type %.1 [template]
// CHECK:STDOUT: %.4: type = unbound_element_type %C, %B [template]
// CHECK:STDOUT: %.5: type = struct_type {.base: %B} [template]
// CHECK:STDOUT: %.6: type = struct_type {.base: %.3} [template]
// CHECK:STDOUT: %.7: type = ptr_type %.5 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
+1 -1
View File
@@ -56,9 +56,9 @@ fn F(c: Class) {
// CHECK:STDOUT: %WithSelf.type: type = fn_type @WithSelf [template]
// CHECK:STDOUT: %WithSelf: %WithSelf.type = struct_value () [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
@@ -39,6 +39,7 @@ fn G() -> i32 {
// CHECK:STDOUT: %.3: type = struct_type {.field: i32} [template]
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
// CHECK:STDOUT: %.4: type = ptr_type %.3 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
+214
View File
@@ -0,0 +1,214 @@
// 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
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/generic/field.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic/field.carbon
// CHECK:STDERR: fail_todo_field.carbon: ERROR: Main//default previously provided by `field.carbon`.
// CHECK:STDERR:
// --- field.carbon
class Class(T:! type) {
var x: T;
}
// TODO: This case only works because the parameters of Class and G have the
// same name and index, so canonicalize to the same type.
fn G(T:! type, c: Class(T)) -> T {
return c.x;
}
// --- fail_todo_field.carbon
class Class(T:! type) {
var x: T;
}
fn F(c: Class(i32)) -> i32 {
// CHECK:STDERR: fail_todo_field.carbon:[[@LINE+8]]:3: ERROR: Cannot implicitly convert from `T` to `i32`.
// CHECK:STDERR: return c.x;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_field.carbon:[[@LINE+4]]:10: ERROR: Cannot implicitly convert from `Class` to `Class`.
// CHECK:STDERR: return c.x;
// CHECK:STDERR: ^~~
// CHECK:STDERR:
return c.x;
}
fn H(U:! type, c: Class(U)) -> U {
// CHECK:STDERR: fail_todo_field.carbon:[[@LINE+7]]:3: ERROR: Cannot implicitly convert from `T` to `U`.
// CHECK:STDERR: return c.x;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_field.carbon:[[@LINE+3]]:10: ERROR: Cannot implicitly convert from `Class` to `Class`.
// CHECK:STDERR: return c.x;
// CHECK:STDERR: ^~~
return c.x;
}
// CHECK:STDOUT: --- field.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %T: type = bind_symbolic_name T 0 [symbolic]
// CHECK:STDOUT: %Class.type: type = generic_class_type @Class [template]
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %Class.1: %Class.type = struct_value () [template]
// CHECK:STDOUT: %Class.2: type = class_type @Class, (%T) [symbolic]
// CHECK:STDOUT: %.2: type = unbound_element_type %Class.2, %T [symbolic]
// CHECK:STDOUT: %.3: type = struct_type {.x: %T} [symbolic]
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
// CHECK:STDOUT: %.4: type = ptr_type %.3 [symbolic]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = %Core
// CHECK:STDOUT: .Class = %Class.decl
// CHECK:STDOUT: .G = %G.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %Core: <namespace> = namespace %Core.import, [template] {}
// CHECK:STDOUT: %Class.decl: %Class.type = class_decl @Class [template = constants.%Class.1] {
// CHECK:STDOUT: %T.loc2_13.1: type = param T
// CHECK:STDOUT: %T.loc2_13.2: type = bind_symbolic_name T 0, %T.loc2_13.1 [symbolic = %T.loc2_13.2 (constants.%T)]
// CHECK:STDOUT: }
// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] {
// CHECK:STDOUT: %T.loc8_6.1: type = param T
// CHECK:STDOUT: @G.%T: type = bind_symbolic_name T 0, %T.loc8_6.1 [symbolic = @G.%T (constants.%T)]
// CHECK:STDOUT: %Class.ref: %Class.type = name_ref Class, %Class.decl [template = constants.%Class.1]
// CHECK:STDOUT: %T.ref.loc8_25: type = name_ref T, @G.%T [symbolic = @G.%T (constants.%T)]
// CHECK:STDOUT: %.loc8_24: init type = call %Class.ref(%T.ref.loc8_25) [symbolic = %.loc8_24 (constants.%Class.2)]
// CHECK:STDOUT: %.loc8_26.1: type = value_of_initializer %.loc8_24 [symbolic = %.loc8_24 (constants.%Class.2)]
// CHECK:STDOUT: %.loc8_26.2: type = converted %.loc8_24, %.loc8_26.1 [symbolic = %.loc8_24 (constants.%Class.2)]
// CHECK:STDOUT: %c.loc8_16.1: file.%.loc8_24 (%Class.2) = param c
// CHECK:STDOUT: @G.%c: file.%.loc8_24 (%Class.2) = bind_name c, %c.loc8_16.1
// CHECK:STDOUT: %T.ref.loc8_32: type = name_ref T, @G.%T [symbolic = @G.%T (constants.%T)]
// CHECK:STDOUT: @G.%return: ref %T = var <return slot>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Class
// CHECK:STDOUT: generic [file.%T.loc2_13.2: type] {
// CHECK:STDOUT: %T.ref: type = name_ref T, file.%T.loc2_13.2 [symbolic = constants.%T]
// CHECK:STDOUT: %.loc3: %.2 = field_decl x, element0 [template]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%Class.2
// CHECK:STDOUT: .x = %.loc3
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @G(%T: type, %c: file.%.loc8_24 (%Class.2)) -> %T
// CHECK:STDOUT: generic [%T: type] {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %c.ref: %Class.2 = name_ref c, %c
// CHECK:STDOUT: %x.ref: %.2 = name_ref x, @Class.%.loc3 [template = @Class.%.loc3]
// CHECK:STDOUT: %.loc9_11.1: ref %T = class_element_access %c.ref, element0
// CHECK:STDOUT: %.loc9_11.2: %T = bind_value %.loc9_11.1
// CHECK:STDOUT: return %.loc9_11.2
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_todo_field.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %T: type = bind_symbolic_name T 0 [symbolic]
// CHECK:STDOUT: %Class.type: type = generic_class_type @Class [template]
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %Class.1: %Class.type = struct_value () [template]
// CHECK:STDOUT: %Class.2: type = class_type @Class, (%T) [symbolic]
// CHECK:STDOUT: %.2: type = unbound_element_type %Class.2, %T [symbolic]
// CHECK:STDOUT: %.3: type = struct_type {.x: %T} [symbolic]
// CHECK:STDOUT: %Int32.type: type = fn_type @Int32 [template]
// CHECK:STDOUT: %Int32: %Int32.type = struct_value () [template]
// CHECK:STDOUT: %Class.3: type = class_type @Class, (i32) [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %.4: type = ptr_type %.3 [symbolic]
// CHECK:STDOUT: %U: type = bind_symbolic_name U 0 [symbolic]
// CHECK:STDOUT: %Class.4: type = class_type @Class, (%U) [symbolic]
// CHECK:STDOUT: %H.type: type = fn_type @H [template]
// CHECK:STDOUT: %H: %H.type = struct_value () [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %import_ref.1: %Int32.type = import_ref ir3, inst+4, loaded [template = constants.%Int32]
// CHECK:STDOUT: %import_ref.2: %Int32.type = import_ref ir3, inst+4, loaded [template = constants.%Int32]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = %Core
// CHECK:STDOUT: .Class = %Class.decl
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: .H = %H.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %Core: <namespace> = namespace %Core.import, [template] {}
// CHECK:STDOUT: %Class.decl: %Class.type = class_decl @Class [template = constants.%Class.1] {
// CHECK:STDOUT: %T.loc2_13.1: type = param T
// CHECK:STDOUT: %T.loc2_13.2: type = bind_symbolic_name T 0, %T.loc2_13.1 [symbolic = %T.loc2_13.2 (constants.%T)]
// CHECK:STDOUT: }
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {
// CHECK:STDOUT: %Class.ref.loc6: %Class.type = name_ref Class, %Class.decl [template = constants.%Class.1]
// CHECK:STDOUT: %int.make_type_32.loc6_15: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc6_14.1: type = value_of_initializer %int.make_type_32.loc6_15 [template = i32]
// CHECK:STDOUT: %.loc6_14.2: type = converted %int.make_type_32.loc6_15, %.loc6_14.1 [template = i32]
// CHECK:STDOUT: %.loc6_14.3: init type = call %Class.ref.loc6(%.loc6_14.2) [template = constants.%Class.3]
// CHECK:STDOUT: %.loc6_18.1: type = value_of_initializer %.loc6_14.3 [template = constants.%Class.3]
// CHECK:STDOUT: %.loc6_18.2: type = converted %.loc6_14.3, %.loc6_18.1 [template = constants.%Class.3]
// CHECK:STDOUT: %c.loc6_6.1: %Class.3 = param c
// CHECK:STDOUT: @F.%c: %Class.3 = bind_name c, %c.loc6_6.1
// CHECK:STDOUT: %int.make_type_32.loc6_24: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc6_24.1: type = value_of_initializer %int.make_type_32.loc6_24 [template = i32]
// CHECK:STDOUT: %.loc6_24.2: type = converted %int.make_type_32.loc6_24, %.loc6_24.1 [template = i32]
// CHECK:STDOUT: @F.%return: ref i32 = var <return slot>
// CHECK:STDOUT: }
// CHECK:STDOUT: %H.decl: %H.type = fn_decl @H [template = constants.%H] {
// CHECK:STDOUT: %U.loc18_6.1: type = param U
// CHECK:STDOUT: @H.%U: type = bind_symbolic_name U 0, %U.loc18_6.1 [symbolic = @H.%U (constants.%U)]
// CHECK:STDOUT: %Class.ref.loc18: %Class.type = name_ref Class, %Class.decl [template = constants.%Class.1]
// CHECK:STDOUT: %U.ref.loc18_25: type = name_ref U, @H.%U [symbolic = @H.%U (constants.%U)]
// CHECK:STDOUT: %.loc18_24: init type = call %Class.ref.loc18(%U.ref.loc18_25) [symbolic = %.loc18_24 (constants.%Class.4)]
// CHECK:STDOUT: %.loc18_26.1: type = value_of_initializer %.loc18_24 [symbolic = %.loc18_24 (constants.%Class.4)]
// CHECK:STDOUT: %.loc18_26.2: type = converted %.loc18_24, %.loc18_26.1 [symbolic = %.loc18_24 (constants.%Class.4)]
// CHECK:STDOUT: %c.loc18_16.1: file.%.loc18_24 (%Class.4) = param c
// CHECK:STDOUT: @H.%c: file.%.loc18_24 (%Class.4) = bind_name c, %c.loc18_16.1
// CHECK:STDOUT: %U.ref.loc18_32: type = name_ref U, @H.%U [symbolic = @H.%U (constants.%U)]
// CHECK:STDOUT: @H.%return: ref %U = var <return slot>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Class
// CHECK:STDOUT: generic [file.%T.loc2_13.2: type] {
// CHECK:STDOUT: %T.ref: type = name_ref T, file.%T.loc2_13.2 [symbolic = constants.%T]
// CHECK:STDOUT: %.loc3: %.2 = field_decl x, element0 [template]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%Class.2
// CHECK:STDOUT: .x = %.loc3
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F(%c: %Class.3) -> i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %c.ref: %Class.3 = name_ref c, %c
// CHECK:STDOUT: %x.ref: %.2 = name_ref x, @Class.%.loc3 [template = @Class.%.loc3]
// CHECK:STDOUT: %.loc15: %T = class_element_access <error>, element0 [template = <error>]
// CHECK:STDOUT: return <error>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @H(%U: type, %c: file.%.loc18_24 (%Class.4)) -> %U
// CHECK:STDOUT: generic [%U: type] {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %c.ref: %Class.4 = name_ref c, %c
// CHECK:STDOUT: %x.ref: %.2 = name_ref x, @Class.%.loc3 [template = @Class.%.loc3]
// CHECK:STDOUT: %.loc26: %T = class_element_access <error>, element0 [template = <error>]
// CHECK:STDOUT: return <error>
// CHECK:STDOUT: }
// CHECK:STDOUT:
+2 -2
View File
@@ -33,12 +33,12 @@ fn G(o: Outer) {
// CHECK:STDOUT: %.2: type = unbound_element_type %Inner, i32 [template]
// CHECK:STDOUT: %.3: type = struct_type {.n: i32} [template]
// CHECK:STDOUT: %.4: type = struct_type {} [template]
// CHECK:STDOUT: %.5: type = ptr_type %.4 [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %.5: type = ptr_type %.3 [template]
// CHECK:STDOUT: %.6: type = ptr_type %.3 [template]
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
// CHECK:STDOUT: %.6: type = ptr_type %.4 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -240,6 +240,7 @@ var x: () = D.C.F();
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %C: type = class_type @C [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: }
@@ -285,6 +286,7 @@ var x: () = D.C.F();
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %C: type = class_type @C [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: }
@@ -330,6 +332,7 @@ var x: () = D.C.F();
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %C: type = class_type @C [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: }
@@ -375,6 +378,7 @@ var x: () = D.C.F();
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %D: type = class_type @D [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %C: type = class_type @C [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
@@ -430,6 +434,7 @@ var x: () = D.C.F();
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %D: type = class_type @D [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %C: type = class_type @C [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
+1
View File
@@ -30,6 +30,7 @@ fn Class.F() -> i32 {
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
+3 -2
View File
@@ -30,7 +30,8 @@ class Class {
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %.3: i32 = int_literal 1 [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: %.4: i32 = int_literal 1 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -82,7 +83,7 @@ class Class {
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() -> i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %.loc17: i32 = int_literal 1 [template = constants.%.3]
// CHECK:STDOUT: %.loc17: i32 = int_literal 1 [template = constants.%.4]
// CHECK:STDOUT: return %.loc17
// CHECK:STDOUT: }
// CHECK:STDOUT:
+1
View File
@@ -45,6 +45,7 @@ fn Run() {
// CHECK:STDOUT: %.4: i32 = int_literal 2 [template]
// CHECK:STDOUT: %Run.type: type = fn_type @Run [template]
// CHECK:STDOUT: %Run: %Run.type = struct_value () [template]
// CHECK:STDOUT: %.5: type = ptr_type %.2 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -44,9 +44,10 @@ impl C as I {
// CHECK:STDOUT: %Int32: %Int32.type = struct_value () [template]
// CHECK:STDOUT: %F.type.2: type = fn_type @F.2 [template]
// CHECK:STDOUT: %F.2: %F.type.2 = struct_value () [template]
// CHECK:STDOUT: %.6: type = ptr_type %.5 [template]
// CHECK:STDOUT: %F.type.3: type = fn_type @F.3 [template]
// CHECK:STDOUT: %F.3: %F.type.3 = struct_value () [template]
// CHECK:STDOUT: %.6: <witness> = interface_witness (%F.3) [template]
// CHECK:STDOUT: %.7: <witness> = interface_witness (%F.3) [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
@@ -89,7 +90,7 @@ impl C as I {
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl: %C as %.1 {
// CHECK:STDOUT: %F.decl: %F.type.3 = fn_decl @F.3 [template = constants.%F.3] {}
// CHECK:STDOUT: %.1: <witness> = interface_witness (%F.decl) [template = constants.%.6]
// CHECK:STDOUT: %.1: <witness> = interface_witness (%F.decl) [template = constants.%.7]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .F = %F.decl
@@ -30,6 +30,7 @@ alias UseOther = I.other;
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %.1: type = interface_type @I [template]
// CHECK:STDOUT: %Self: %.1 = bind_symbolic_name Self 0 [symbolic]
// CHECK:STDOUT: %.2: type = tuple_type () [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
+1
View File
@@ -162,6 +162,7 @@ fn Main() {
// CHECK:STDOUT: %.2: type = struct_type {} [template]
// CHECK:STDOUT: %Main.type: type = fn_type @Main [template]
// CHECK:STDOUT: %Main: %Main.type = struct_value () [template]
// CHECK:STDOUT: %.3: type = ptr_type %.2 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {