Support detecting begin()/end() methods for range-for loops (#7185)

This commit adds support for range-based for loops using C++ types. It's
currently limited to detecting that `r.begin()` and `r.end()` are
available. We should be able to add full support for methods after #7181
is merged.

Support for ADL is still a work-in-progress, and will be added at a
later time.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
Christopher Di Bella
2026-05-12 19:03:55 +00:00
committed by GitHub
co-authored by Richard Smith
parent dd7cfdb149
commit 2aaa061688
6 changed files with 487 additions and 0 deletions
+7
View File
@@ -30,3 +30,10 @@ impl forall [T:! Copy & Destroy, N:! IntLiteral()]
}
}
}
interface CppRangeForIterate {
let Iterator:! type;
let Sentinel:! type;
fn Begin[ref self: Self]() -> Iterator;
fn End[ref self: Self]() -> Sentinel;
}
+1
View File
@@ -33,6 +33,7 @@ CARBON_CORE_IDENTIFIER(Char)
CARBON_CORE_IDENTIFIER(Convert)
CARBON_CORE_IDENTIFIER(Copy)
CARBON_CORE_IDENTIFIER(CppCompat)
CARBON_CORE_IDENTIFIER(CppRangeForIterate)
CARBON_CORE_IDENTIFIER(CppUnsafeDeref)
CARBON_CORE_IDENTIFIER(Dec)
CARBON_CORE_IDENTIFIER(Default)
+119
View File
@@ -4,6 +4,8 @@
#include "toolchain/check/cpp/impl_lookup.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Sema.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/core_identifier.h"
@@ -11,6 +13,7 @@
#include "toolchain/check/cpp/location.h"
#include "toolchain/check/cpp/operators.h"
#include "toolchain/check/cpp/overload_resolution.h"
#include "toolchain/check/cpp/type_mapping.h"
#include "toolchain/check/custom_witness.h"
#include "toolchain/check/impl.h"
#include "toolchain/check/impl_lookup.h"
@@ -353,6 +356,118 @@ static auto BuildCppComparisonWitness(
query_specific_interface_id, operators);
}
static auto LookupCppMethod(
Context& context, clang::Sema& clang_sema, SemIR::LocId loc_id,
const clang::DeclarationNameInfo& name_info,
clang::CXXRecordDecl* class_decl,
[[maybe_unused]] SemIR::ConstantId query_self_const_id) -> SemIR::InstId {
constexpr auto LookupKind = clang::Sema::LookupMemberName;
auto lookup_info = clang::LookupResult(clang_sema, name_info, LookupKind);
clang_sema.LookupQualifiedName(lookup_info, class_decl);
if (lookup_info.empty()) {
return SemIR::InstId::None;
}
if (!lookup_info.isSingleResult()) {
context.TODO(loc_id, "{method_name} overload sets unsupported");
return SemIR::ErrorInst::InstId;
}
auto decl_info = DeclInfo{
.decl = *lookup_info.begin(),
.signature = {.num_params = 0},
};
return GetFunctionId(context, loc_id, decl_info);
}
static auto LookupCppUnqualified(Context& context, clang::Sema& clang_sema,
SemIR::LocId loc_id,
const clang::DeclarationNameInfo& name_info,
clang::CXXRecordDecl* class_decl,
SemIR::ConstantId query_self_const_id)
-> SemIR::InstId {
(void)clang_sema;
(void)name_info;
(void)class_decl;
(void)query_self_const_id;
context.TODO(loc_id, "support ADL begin/end");
return SemIR::ErrorInst::InstId;
}
using LookupBeginEndCallees = auto(Context&, clang::Sema&, SemIR::LocId,
const clang::DeclarationNameInfo&,
clang::CXXRecordDecl*, SemIR::ConstantId)
-> SemIR::InstId;
static auto BuildCppRangeForIterateWitnessImpl(
Context& context, SemIR::LocId loc_id,
LookupBeginEndCallees range_for_lookup, clang::CXXRecordDecl* class_decl,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
auto& clang_sema = context.clang_sema();
auto begin_name_info = clang::DeclarationNameInfo(
&clang_sema.PP.getIdentifierTable().get("begin"),
clang::SourceLocation());
auto begin_fn_id =
range_for_lookup(context, clang_sema, loc_id, begin_name_info, class_decl,
query_self_const_id);
if (begin_fn_id == SemIR::InstId::None ||
begin_fn_id == SemIR::ErrorInst::InstId) {
return begin_fn_id;
}
auto begin_result_type_id =
context.functions()
.Get(context.insts()
.GetAs<SemIR::FunctionDecl>(begin_fn_id)
.function_id)
.return_type_inst_id;
if (begin_result_type_id == SemIR::ErrorInst::InstId ||
begin_result_type_id == SemIR::InstId::None) {
return SemIR::ErrorInst::InstId;
}
auto end_name_info = clang::DeclarationNameInfo(
&clang_sema.PP.getIdentifierTable().get("end"), clang::SourceLocation());
auto end_fn_id = range_for_lookup(context, clang_sema, loc_id, end_name_info,
class_decl, query_self_const_id);
if (end_fn_id == SemIR::InstId::None ||
end_fn_id == SemIR::ErrorInst::InstId) {
return end_fn_id;
}
auto end_result_type_id =
context.functions()
.Get(
context.insts().GetAs<SemIR::FunctionDecl>(end_fn_id).function_id)
.return_type_inst_id;
if (end_result_type_id == SemIR::ErrorInst::InstId ||
end_result_type_id == SemIR::InstId::None) {
return SemIR::ErrorInst::InstId;
}
return BuildCustomWitness(
context, loc_id, query_self_const_id, query_specific_interface_id,
{begin_result_type_id, end_result_type_id, begin_fn_id, end_fn_id});
}
static auto BuildCppRangeForIterateWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
if (auto with_members = BuildCppRangeForIterateWitnessImpl(
context, loc_id, LookupCppMethod, class_decl, query_self_const_id,
query_specific_interface_id);
with_members != SemIR::InstId::None) {
return with_members;
}
return BuildCppRangeForIterateWitnessImpl(
context, loc_id, LookupCppUnqualified, class_decl, query_self_const_id,
query_specific_interface_id);
}
auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
SemIR::CoreInterface core_interface,
SemIR::ConstantId query_self_const_id,
@@ -417,6 +532,10 @@ auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
return BuildDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id);
case SemIR::CoreInterface::CppRangeForIterate:
return BuildCppRangeForIterateWitness(
context, loc_id, query_self_const_id, query_specific_interface_id);
// IntFitsIn is for Carbon integer types only.
case SemIR::CoreInterface::IntFitsIn:
return SemIR::InstId::None;
+1
View File
@@ -704,6 +704,7 @@ auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
case SemIR::CoreInterface::AddAssignWith:
case SemIR::CoreInterface::AddWith:
case SemIR::CoreInterface::Copy:
case SemIR::CoreInterface::CppRangeForIterate:
case SemIR::CoreInterface::CppUnsafeDeref:
case SemIR::CoreInterface::Dec:
case SemIR::CoreInterface::Default:
+358
View File
@@ -0,0 +1,358 @@
// 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/full.carbon
//
// EXTRA-ARGS: --clang-arg=-std=c++20
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/range_for.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/range_for.carbon
// --- methods_return_same_type.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''c++
class MutableRange {
struct Iterator {
auto operator*() const -> int&;
auto operator++() -> Iterator&;
friend auto operator==(Iterator, Iterator) -> bool;
friend auto operator!=(Iterator, Iterator) -> bool;
};
public:
auto begin() -> Iterator;
auto end() -> Iterator;
};
class ConstRange {
struct Iterator {
auto operator*() const -> const int&;
auto operator++() -> Iterator&;
friend auto operator==(Iterator, Iterator) -> bool;
friend auto operator!=(Iterator, Iterator) -> bool;
};
public:
auto begin() const -> Iterator;
auto end() const -> Iterator;
};
''';
// TODO: uncomment after #7181 is merged.
// fn CppRangeForIterate[R:! Core.CppRangeForIterate where .Iterator impls Core.Destroy and .Sentinel impls Core.Destroy](r: R) {
// //@dump-sem-ir-begin
// r.Begin();
// r.End();
// //@dump-sem-ir-end
// }
fn Test(var m: Cpp.MutableRange, var c: Cpp.ConstRange) {
// TODO: replace with Test(m) when #7181 is merged.
m.(Core.CppRangeForIterate.Begin)();
m.(Core.CppRangeForIterate.End)();
// TODO: replace with Test(c) when #7181 is merged.
c.(Core.CppRangeForIterate.Begin)();
c.(Core.CppRangeForIterate.End)();
}
// --- methods_return_different_types.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''c++
class MutableRange {
struct ValueType {};
struct Iterator {
auto operator*() const -> ValueType&;
auto operator++() -> Iterator&;
};
struct Sentinel {
friend auto operator==(Sentinel, Iterator) -> bool;
friend auto operator!=(Sentinel, Iterator) -> bool;
};
public:
auto begin() -> Iterator;
auto end() -> Sentinel;
};
class ConstRange {
struct ValueType {};
struct Iterator {
auto operator*() const -> ValueType;
auto operator++() -> Iterator&;
};
struct Sentinel {
friend auto operator==(Sentinel, Iterator) -> bool;
friend auto operator!=(Sentinel, Iterator) -> bool;
};
public:
auto begin() const -> Iterator;
auto end() const -> Sentinel;
};
''';
// TODO: uncomment after #7181 is merged.
// fn Test[R:! Core.CppRangeForIterate where .Iterator impls Core.Destroy and .Sentinel impls Core.Destroy](r: R) {
// //@dump-sem-ir-begin
// r.Begin();
// r.End();
// //@dump-sem-ir-end
// }
fn TestDriver(var m: Cpp.MutableRange, var c: Cpp.ConstRange) {
// TODO: replace with Test(m) when #7181 is merged.
m.(Core.CppRangeForIterate.Begin)();
m.(Core.CppRangeForIterate.End)();
// TODO: replace with Test(c) when #7181 is merged.
c.(Core.CppRangeForIterate.Begin)();
c.(Core.CppRangeForIterate.End)();
}
// --- fail_todo_adl_return_same_types.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''c++
namespace N {
class MutableRange {
struct Iterator {
auto operator*() const -> int&;
auto operator++() -> Iterator&;
friend auto operator==(Iterator, Iterator) -> bool;
friend auto operator!=(Iterator, Iterator) -> bool;
};
public:
friend auto begin(MutableRange&) -> Iterator;
friend auto end(MutableRange&) -> Iterator;
};
class ConstRange {
struct Iterator {
auto operator*() const -> const int&;
auto operator++() -> Iterator&;
friend auto operator==(Iterator, Iterator) -> bool;
friend auto operator!=(Iterator, Iterator) -> bool;
};
public:
friend auto begin(const ConstRange&) -> Iterator;
friend auto end(const ConstRange&) -> Iterator;
};
} // namespace N
''';
// TODO: uncomment after #7181 is merged.
// fn CppRangeForIterate[R:! Core.CppRangeForIterate where .Iterator impls Core.Destroy and .Sentinel impls Core.Destroy](r: R) {
// //@dump-sem-ir-begin
// r.Begin();
// r.End();
// //@dump-sem-ir-end
// }
fn Test(var m: Cpp.N.MutableRange, c: Cpp.N.ConstRange) {
// TODO: replace with Test(m) when #7181 is merged.
// CHECK:STDERR: fail_todo_adl_return_same_types.carbon:[[@LINE+4]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: m.(Core.CppRangeForIterate.Begin)();
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
m.(Core.CppRangeForIterate.Begin)();
m.(Core.CppRangeForIterate.End)();
// TODO: replace with Test(c) when #7181 is merged.
// CHECK:STDERR: fail_todo_adl_return_same_types.carbon:[[@LINE+4]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: c.(Core.CppRangeForIterate.Begin)();
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
c.(Core.CppRangeForIterate.Begin)();
c.(Core.CppRangeForIterate.End)();
}
// --- fail_todo_adl_returns_different_types.carbon
library "[[@TEST_NAME]]";
import Cpp inline '''c++
namespace N {
class MutableRange {
struct ValueType {};
struct Iterator {
auto operator*() const -> ValueType&;
auto operator++() -> Iterator&;
};
struct Sentinel {
friend auto operator==(Sentinel, Iterator) -> bool;
friend auto operator!=(Sentinel, Iterator) -> bool;
};
public:
friend auto begin(MutableRange&) -> Iterator;
friend auto end(MutableRange&) -> Sentinel;
};
class ConstRange {
struct ValueType {};
struct Iterator {
auto operator*() const -> ValueType;
auto operator++() -> Iterator&;
};
struct Sentinel {
friend auto operator==(Sentinel, Iterator) -> bool;
friend auto operator!=(Sentinel, Iterator) -> bool;
};
public:
friend auto begin(const ConstRange&) -> Iterator;
friend auto end(const ConstRange&) -> Sentinel;
};
} // namespace N
''';
// TODO: uncomment after #7181 is merged.
// fn Test[R:! Core.CppRangeForIterate where .Iterator impls Core.Destroy and .Sentinel impls Core.Destroy](r: R) {
// //@dump-sem-ir-begin
// r.Begin();
// r.End();
// //@dump-sem-ir-end
// }
fn TestDriver(var m: Cpp.N.MutableRange, c: Cpp.N.ConstRange) {
// TODO: replace with Test(m) when #7181 is merged.
// CHECK:STDERR: fail_todo_adl_returns_different_types.carbon:[[@LINE+4]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: m.(Core.CppRangeForIterate.Begin)();
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
m.(Core.CppRangeForIterate.Begin)();
m.(Core.CppRangeForIterate.End)();
// TODO: replace with Test(c) when #7181 is merged.
// CHECK:STDERR: fail_todo_adl_returns_different_types.carbon:[[@LINE+4]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: c.(Core.CppRangeForIterate.Begin)();
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
c.(Core.CppRangeForIterate.Begin)();
c.(Core.CppRangeForIterate.End)();
}
// --- fail_todo_never_valid.carbon
// TODO: rename to `fail_never_valid.carbon` after ADL is supported.
library "[[@TEST_NAME]]";
import Cpp inline '''c++
class NoBeginEnd {};
struct NoBeginMethod {
auto end() -> int;
};
struct NoEndMethod {
auto begin();
};
struct NoBeginADL {
friend auto end(NoBeginADL) -> int;
};
struct NoEndADL {
friend auto begin(NoEndADL) -> int;
};
struct BeginMethodEndADL {
auto begin() -> int;
friend auto end(BeginMethodEndADL) -> int;
};
struct BeginADLEndMethod {
friend auto begin(BeginADLEndMethod) -> int;
auto end() -> int;
};
''';
fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
fn TestDriver(no_begin_end: Cpp.NoBeginEnd,
no_begin_method: Cpp.NoBeginMethod,
no_end_method: Cpp.NoEndMethod,
no_begin_adl: Cpp.NoBeginADL,
no_end_adl: Cpp.NoEndADL,
begin_method_end_adl: Cpp.BeginMethodEndADL,
begin_adl_end_method: Cpp.BeginADLEndMethod)
{
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+7]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: Test(no_begin_end);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-13]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(no_begin_end);
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+7]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: Test(no_begin_method);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-22]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(no_begin_method);
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+10]]:21: error: function 'begin' with deduced return type cannot be used before it is defined [CppInteropParseError]
// CHECK:STDERR: 74 | Test(no_end_method);
// CHECK:STDERR: | ^
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-53]]:8: note: 'begin' declared here [CppInteropParseNote]
// CHECK:STDERR: 14 | auto begin();
// CHECK:STDERR: | ^
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-34]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(no_end_method);
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+7]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: Test(no_begin_adl);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-43]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(no_begin_adl);
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+7]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: Test(no_end_adl);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-52]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(no_end_adl);
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+7]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: Test(begin_method_end_adl);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-61]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(begin_method_end_adl);
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE+7]]:3: error: semantics TODO: `support ADL begin/end` [SemanticsTodo]
// CHECK:STDERR: Test(begin_adl_end_method);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_never_valid.carbon:[[@LINE-70]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Test[R:! Core.CppRangeForIterate](unused r: R) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Test(begin_adl_end_method);
}
+1
View File
@@ -19,6 +19,7 @@
CARBON_SEM_IR_CORE_INTERFACE_KIND(AddAssignWith)
CARBON_SEM_IR_CORE_INTERFACE_KIND(AddWith)
CARBON_SEM_IR_CORE_INTERFACE_KIND(Copy)
CARBON_SEM_IR_CORE_INTERFACE_KIND(CppRangeForIterate)
CARBON_SEM_IR_CORE_INTERFACE_KIND(CppUnsafeDeref)
CARBON_SEM_IR_CORE_INTERFACE_KIND(Dec)
CARBON_SEM_IR_CORE_INTERFACE_KIND(Default)