Fix accessing members of const/partial types (#7406)

In `PerformActionHelper`, use the unqualified type for lookup.

In `PerformInstanceBinding`, propagate qualifiers to the unbound element
type's class type when doing the `ConvertToValueOrRefOfType` conversion,
and to the element type when forming the `ClassElementAccess` instr
(except for `partial`, which is only used if the member being accessed
is `base`).

In handle_operator.cpp, prevent assignment to a reference to a const
type.
This commit is contained in:
Nicholas Bishop
2026-06-25 00:07:39 +00:00
committed by GitHub
parent 885b1110d5
commit 36d9bed4ca
5 changed files with 202 additions and 11 deletions
+6 -2
View File
@@ -104,9 +104,13 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorEqualId node_id)
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId();
auto lhs_type_id = context.insts().Get(lhs_id).type_id();
auto lhs_quals =
context.types().GetUnqualifiedTypeAndQualifiers(lhs_type_id).second;
if (auto lhs_cat = SemIR::GetExprCategory(context.sem_ir(), lhs_id);
lhs_cat != SemIR::ExprCategory::DurableRef &&
lhs_cat != SemIR::ExprCategory::Error) {
(lhs_cat != SemIR::ExprCategory::DurableRef &&
lhs_cat != SemIR::ExprCategory::Error) ||
lhs_quals.HasAnyOf(SemIR::TypeQualifiers::Const)) {
CARBON_DIAGNOSTIC(AssignmentToNonAssignable, Error,
"expression is not assignable");
context.emitter().Emit(lhs_node, AssignmentToNonAssignable);
+26 -9
View File
@@ -406,11 +406,18 @@ static auto PerformInstanceBinding(Context& context, SemIR::LocId loc_id,
if (auto unbound_element_type =
context.types().TryGetAs<SemIR::UnboundElementType>(
context.insts().Get(member_id).type_id())) {
auto base_type_id = context.insts().Get(base_id).type_id();
auto [unqualified_base_type_id, qualifiers] =
context.types().GetUnqualifiedTypeAndQualifiers(base_type_id);
// Convert the base to the type of the element if necessary.
base_id = ConvertToValueOrRefOfType(
context, loc_id, base_id,
context.types().GetTypeIdForTypeInstId(
unbound_element_type->class_type_inst_id));
auto element_class_type_id =
GetQualifiedType(context,
context.types().GetTypeIdForTypeInstId(
unbound_element_type->class_type_inst_id),
qualifiers);
base_id = ConvertToValueOrRefOfType(context, loc_id, base_id,
element_class_type_id);
// Find the specified element, which could be either a field or a base
// class, and build an element access expression.
@@ -419,12 +426,18 @@ static auto PerformInstanceBinding(Context& context, SemIR::LocId loc_id,
"Non-constant value {0} of unbound element type",
context.insts().Get(member_id));
auto index = GetClassElementIndex(context, element_id);
// Only propagate the `partial` qualifier to the `base` field.
if (!context.insts().Is<SemIR::BaseDecl>(element_id)) {
qualifiers.Remove(SemIR::TypeQualifiers::Partial);
}
auto access_type_id =
GetQualifiedType(context,
context.types().GetTypeIdForTypeInstId(
unbound_element_type->element_type_inst_id),
qualifiers);
auto access_id = GetOrAddInst<SemIR::ClassElementAccess>(
context, loc_id,
{.type_id = context.types().GetTypeIdForTypeInstId(
unbound_element_type->element_type_inst_id),
.base_id = base_id,
.index = index});
{.type_id = access_type_id, .base_id = base_id, .index = index});
if (SemIR::GetExprCategory(context.sem_ir(), base_id) ==
SemIR::ExprCategory::Value &&
SemIR::GetExprCategory(context.sem_ir(), access_id) !=
@@ -582,7 +595,11 @@ static auto PerformActionHelper(Context& context, SemIR::LocId loc_id,
base_id = ConvertToValueOrRefExpr(context, base_id);
base_type_id = context.insts().Get(base_id).type_id();
auto lookup_const_id = context.types().GetConstantId(base_type_id);
// If the type has qualifiers, use the unqualified type for lookup.
auto unqualified_base_type_id =
context.types().GetUnqualifiedTypeAndQualifiers(base_type_id).first;
auto lookup_const_id =
context.types().GetConstantId(unqualified_base_type_id);
// TODO: If the type is a facet, we look through it into the facet's type (a
// FacetType) for names. According to the design, we shouldn't need to do
+47
View File
@@ -0,0 +1,47 @@
// 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
//
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/partial/access.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/partial/access.carbon
// --- common.carbon
package Common;
base class A {
var n: i32;
virtual fn F(self);
}
base class B {
extend base: A;
}
// --- partial_member_access.carbon
library "[[@TEST_NAME]]";
import Common;
fn F(ref pb: partial Common.B) {
let ref _: i32 = pb.n;
let ref _: partial Common.A = pb.base;
}
// --- fail_invalid_partial_member_access.carbon
library "[[@TEST_NAME]]";
import Common;
fn F(ref pb: partial Common.B) {
// CHECK:STDERR: fail_invalid_partial_member_access.carbon:[[@LINE+7]]:27: error: cannot implicitly convert expression of type `partial Common.A` to `Common.A` [ConversionFailure]
// CHECK:STDERR: let ref _: Common.A = pb.base;
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR: fail_invalid_partial_member_access.carbon:[[@LINE+4]]:27: note: type `partial Common.A` does not implement interface `Core.ImplicitAs(Common.A)` [MissingImplInMemberAccessInContext]
// CHECK:STDERR: let ref _: Common.A = pb.base;
// CHECK:STDERR: ^~~~~~~
// CHECK:STDERR:
let ref _: Common.A = pb.base;
}
+52
View File
@@ -0,0 +1,52 @@
// 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
//
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/const/access.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/const/access.carbon
// --- const_member_access.carbon
library "[[@TEST_NAME]]";
class C {
var x: ();
}
fn F(c: const C) {
c.x;
}
// --- fail_const_member_assign.carbon
library "[[@TEST_NAME]]";
class C {
var x: ();
}
fn F(c: const C) {
// CHECK:STDERR: fail_const_member_assign.carbon:[[@LINE+4]]:3: error: expression is not assignable [AssignmentToNonAssignable]
// CHECK:STDERR: c.x = ();
// CHECK:STDERR: ^~~
// CHECK:STDERR:
c.x = ();
}
// --- fail_const_ref_member_assign.carbon
library "[[@TEST_NAME]]";
class C {
var x: ();
}
fn F(ref c: const C) {
// CHECK:STDERR: fail_const_ref_member_assign.carbon:[[@LINE+4]]:3: error: expression is not assignable [AssignmentToNonAssignable]
// CHECK:STDERR: c.x = ();
// CHECK:STDERR: ^~~
// CHECK:STDERR:
c.x = ();
}
@@ -0,0 +1,71 @@
// 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
//
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/const/access.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/const/access.carbon
// --- const_member_access.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''
struct S {
int x;
};
const S& Get();
''';
fn F() {
Cpp.Get().x;
}
// --- fail_const_member_assign.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''
struct S {
int x;
};
const S& Get();
''';
fn F() {
// CHECK:STDERR: fail_const_member_assign.carbon:[[@LINE+11]]:3: error: expression is not assignable [AssignmentToNonAssignable]
// CHECK:STDERR: Cpp.Get().x = 123;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_const_member_assign.carbon:[[@LINE+7]]:3: error: cannot implicitly convert expression of type `Core.IntLiteral` to `const i32` [ConversionFailure]
// CHECK:STDERR: Cpp.Get().x = 123;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_const_member_assign.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(const i32)` [MissingImplInMemberAccessInContext]
// CHECK:STDERR: Cpp.Get().x = 123;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Cpp.Get().x = 123;
}
// --- fail_const_member_assign_cast.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''
struct S {
int x;
};
const S& Get();
''';
fn F() {
// CHECK:STDERR: fail_const_member_assign_cast.carbon:[[@LINE+4]]:3: error: expression is not assignable [AssignmentToNonAssignable]
// CHECK:STDERR: Cpp.Get().x = 123 as i32;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
Cpp.Get().x = 123 as i32;
}