mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Overload resolution for C++ operators (#6092)
Multiple overloads for the same operator are now resolved using overload resolution. This change doesn't try to solve all issues with operator lookup. Moved the operator lookup logic from `import` to `operators` and changed it to take the args into account. Use `Sema::LookupOverloadedBinOp()` (with ADL) when looking up operator functions to create an overload set. Verified all demos in #6017, #6020 and #6024 still work. C++ Interop Demo: ```c++ // my_number.h class MyNumber { public: explicit MyNumber(int value) : value_(value) {} auto value() const -> int { return value_; } private: int value_; }; class NotMyNumber {}; auto operator+(MyNumber lhs, MyNumber rhs) -> MyNumber; auto operator+(NotMyNumber lhs, NotMyNumber rhs) -> NotMyNumber; ``` ```c++ // my_number.cpp #include "my_number.h" auto operator+(MyNumber lhs, MyNumber rhs) -> MyNumber { return MyNumber(lhs.value() + rhs.value()); } auto operator+(NotMyNumber lhs, NotMyNumber /*rhs*/) -> NotMyNumber { return lhs; } ``` ```carbon // main.carbon library "Main"; import Core library "io"; import Cpp library "my_number.h"; fn Run() -> i32 { // Arithmetic var num1: Cpp.MyNumber = Cpp.MyNumber.MyNumber(14); var num2: Cpp.MyNumber = Cpp.MyNumber.MyNumber(5); Core.Print(num1.value()); Core.Print(num2.value()); Core.Print((num1 + num2).value()); return 0; } ``` **After this change:** ```shell $ clang -c my_number.cpp $ bazel-bin/toolchain/carbon compile main.carbon $ bazel-bin/toolchain/carbon link my_number.o main.o --output=demo $ ./demo 14 5 19 ``` **Before this change** ```shell $ bazel-bin/toolchain/carbon compile main.carbon main.carbon:14:15: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 3` Core.Print((num1 + num2).value()); ^~~~~~~~~~~ main.carbon:14:15: note: in `Cpp` operator `AddWith` lookup Core.Print((num1 + num2).value()); ^~~~~~~~~~~ ``` Part of https://github.com/carbon-language/carbon-lang/issues/5995.
This commit is contained in:
@@ -23,6 +23,7 @@ cc_library(
|
||||
"convert.cpp",
|
||||
"cpp/custom_type_mapping.cpp",
|
||||
"cpp/import.cpp",
|
||||
"cpp/operators.cpp",
|
||||
"cpp/overload_resolution.cpp",
|
||||
"cpp/thunk.cpp",
|
||||
"cpp/type_mapping.cpp",
|
||||
@@ -72,6 +73,7 @@ cc_library(
|
||||
"convert.h",
|
||||
"cpp/custom_type_mapping.h",
|
||||
"cpp/import.h",
|
||||
"cpp/operators.h",
|
||||
"cpp/overload_resolution.h",
|
||||
"cpp/thunk.h",
|
||||
"cpp/type_mapping.h",
|
||||
|
||||
@@ -527,41 +527,6 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
|
||||
return lookup;
|
||||
}
|
||||
|
||||
// Looks up the given declaration name in the Clang AST in a specific scope.
|
||||
// Returns the found declaration and its access. If not found, returns
|
||||
// `nullopt`. If there's not a single result, returns `nullptr` and default
|
||||
// access.
|
||||
static auto ClangLookupDeclarationName(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId scope_id,
|
||||
clang::DeclarationName name)
|
||||
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
|
||||
auto lookup = ClangLookup(context, scope_id, name);
|
||||
if (!lookup) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
|
||||
nullptr, clang::AccessSpecifier::AS_none};
|
||||
|
||||
// Access checks are performed separately by the Carbon name lookup logic.
|
||||
lookup->suppressAccessDiagnostics();
|
||||
|
||||
if (!lookup->isSingleResult()) {
|
||||
// Clang will diagnose ambiguous lookup results for us.
|
||||
if (!lookup->isAmbiguous()) {
|
||||
context.TODO(loc_id,
|
||||
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
|
||||
"find a single result; LookupResultKind: {0}",
|
||||
static_cast<int>(lookup->getResultKind())));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
|
||||
return result;
|
||||
}
|
||||
|
||||
// Looks up for constructors in the class scope and returns the lookup result.
|
||||
static auto ClangConstructorLookup(Context& context,
|
||||
SemIR::NameScopeId scope_id)
|
||||
@@ -1894,11 +1859,8 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
// Attempts to import a set of declarations. Returns `false` if an error was
|
||||
// produced, `true` otherwise.
|
||||
// TODO: Merge overload set and operators and remove the `is_overload_set`
|
||||
// param.
|
||||
static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
|
||||
ImportWorklist& worklist,
|
||||
bool is_overload_set = false) -> bool {
|
||||
ImportWorklist& worklist) -> bool {
|
||||
// Walk the dependency graph in depth-first order, and import declarations
|
||||
// once we've imported all of their dependencies.
|
||||
while (!worklist.empty()) {
|
||||
@@ -1924,7 +1886,7 @@ static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
|
||||
// Functions that are part of the overload set are imported at a later
|
||||
// point, once the overload resolution has selected the suitable function
|
||||
// for the call.
|
||||
if (is_overload_set && decl->getAsFunction()) {
|
||||
if (decl->getAsFunction()) {
|
||||
continue;
|
||||
}
|
||||
auto inst_id = ImportDeclAfterDependencies(context, loc_id, decl);
|
||||
@@ -2056,10 +2018,9 @@ static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
|
||||
return inst_id;
|
||||
}
|
||||
|
||||
// Imports an overloaded function set from Clang to Carbon.
|
||||
static auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
|
||||
SemIR::NameId name_id,
|
||||
const clang::UnresolvedSet<4>& overload_set)
|
||||
auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
|
||||
SemIR::NameId name_id,
|
||||
const clang::UnresolvedSet<4>& overload_set)
|
||||
-> SemIR::InstId {
|
||||
SemIR::CppOverloadSetId overload_set_id = context.cpp_overload_sets().Add(
|
||||
SemIR::CppOverloadSet{.name_id = name_id,
|
||||
@@ -2102,7 +2063,7 @@ static auto ImportOverloadSetAndDependencies(
|
||||
for (clang::NamedDecl* fn_decl : overloaded_set) {
|
||||
AddDependentDecl(context, fn_decl, worklist);
|
||||
}
|
||||
if (!ImportDeclSet(context, loc_id, worklist, true)) {
|
||||
if (!ImportDeclSet(context, loc_id, worklist)) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
return ImportCppOverloadSet(context, scope_id, name_id, overloaded_set);
|
||||
@@ -2213,187 +2174,6 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
|
||||
overload_set);
|
||||
}
|
||||
|
||||
static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
|
||||
llvm::StringLiteral interface_name,
|
||||
llvm::StringLiteral op_name)
|
||||
-> std::optional<clang::OverloadedOperatorKind> {
|
||||
// Unary operators.
|
||||
if (interface_name == "Destroy" || interface_name == "As" ||
|
||||
interface_name == "ImplicitAs") {
|
||||
// TODO: Support destructors and conversions.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Increment and Decrement.
|
||||
if (interface_name == "Inc") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PlusPlus;
|
||||
}
|
||||
if (interface_name == "Dec") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_MinusMinus;
|
||||
}
|
||||
|
||||
// Arithmetic.
|
||||
if (interface_name == "Negate") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Minus;
|
||||
}
|
||||
|
||||
// Binary operators.
|
||||
|
||||
// Arithmetic Operators.
|
||||
if (interface_name == "AddWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Plus;
|
||||
}
|
||||
if (interface_name == "SubWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Minus;
|
||||
}
|
||||
if (interface_name == "MulWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Star;
|
||||
}
|
||||
if (interface_name == "DivWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Slash;
|
||||
}
|
||||
if (interface_name == "ModWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Percent;
|
||||
}
|
||||
|
||||
// Bitwise Operators.
|
||||
if (interface_name == "BitAndWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Amp;
|
||||
}
|
||||
if (interface_name == "BitOrWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Pipe;
|
||||
}
|
||||
if (interface_name == "BitXorWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Caret;
|
||||
}
|
||||
if (interface_name == "LeftShiftWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_LessLess;
|
||||
}
|
||||
if (interface_name == "RightShiftWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_GreaterGreater;
|
||||
}
|
||||
|
||||
// Compound Assignment Arithmetic Operators.
|
||||
if (interface_name == "AddAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PlusEqual;
|
||||
}
|
||||
if (interface_name == "SubAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_MinusEqual;
|
||||
}
|
||||
if (interface_name == "MulAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_StarEqual;
|
||||
}
|
||||
if (interface_name == "DivAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_SlashEqual;
|
||||
}
|
||||
if (interface_name == "ModAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PercentEqual;
|
||||
}
|
||||
|
||||
// Compound Assignment Bitwise Operators.
|
||||
if (interface_name == "BitAndAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_AmpEqual;
|
||||
}
|
||||
if (interface_name == "BitOrAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PipeEqual;
|
||||
}
|
||||
if (interface_name == "BitXorAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_CaretEqual;
|
||||
}
|
||||
// TODO: Add support for `LeftShiftAssignWith` (`OO_LessLessEqual`) and
|
||||
// `RightShiftAssignWith` (`OO_GreaterGreaterEqual`) when references are
|
||||
// supported.
|
||||
|
||||
// Relational Operators.
|
||||
if (interface_name == "EqWith") {
|
||||
if (op_name == "Equal") {
|
||||
return clang::OO_EqualEqual;
|
||||
}
|
||||
CARBON_CHECK(op_name == "NotEqual");
|
||||
return clang::OO_ExclaimEqual;
|
||||
}
|
||||
if (interface_name == "OrderedWith") {
|
||||
if (op_name == "Less") {
|
||||
return clang::OO_Less;
|
||||
}
|
||||
if (op_name == "Greater") {
|
||||
return clang::OO_Greater;
|
||||
}
|
||||
if (op_name == "LessOrEquivalent") {
|
||||
return clang::OO_LessEqual;
|
||||
}
|
||||
CARBON_CHECK(op_name == "GreaterOrEquivalent");
|
||||
return clang::OO_GreaterEqual;
|
||||
}
|
||||
|
||||
context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
|
||||
interface_name));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId scope_id, Operator op)
|
||||
-> SemIR::ScopeLookupResult {
|
||||
Diagnostics::AnnotationScope annotate_diagnostics(
|
||||
&context.emitter(), [&](auto& builder) {
|
||||
CARBON_DIAGNOSTIC(InCppOperatorLookup, Note,
|
||||
"in `Cpp` operator `{0}` lookup", std::string);
|
||||
builder.Note(loc_id, InCppOperatorLookup, op.interface_name.str());
|
||||
});
|
||||
|
||||
auto op_kind =
|
||||
GetClangOperatorKind(context, loc_id, op.interface_name, op.op_name);
|
||||
if (!op_kind) {
|
||||
return SemIR::ScopeLookupResult::MakeNotFound();
|
||||
}
|
||||
|
||||
// TODO: We should do ADL-only lookup for operators
|
||||
// (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
|
||||
// into C++ types. See
|
||||
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2316950123
|
||||
auto decl_and_access = ClangLookupDeclarationName(
|
||||
context, loc_id, scope_id,
|
||||
context.ast_context().DeclarationNames.getCXXOperatorName(*op_kind));
|
||||
|
||||
if (!decl_and_access) {
|
||||
return SemIR::ScopeLookupResult::MakeNotFound();
|
||||
}
|
||||
auto [decl, access] = *decl_and_access;
|
||||
if (!decl) {
|
||||
return SemIR::ScopeLookupResult::MakeError();
|
||||
}
|
||||
|
||||
SemIR::InstId inst_id = ImportDeclAndDependencies(context, loc_id, decl);
|
||||
if (!inst_id.has_value()) {
|
||||
return SemIR::ScopeLookupResult::MakeNotFound();
|
||||
}
|
||||
|
||||
SemIR::AccessKind access_kind = MapAccess(access);
|
||||
return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
|
||||
access_kind);
|
||||
}
|
||||
|
||||
auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ClassId class_id,
|
||||
SemIR::ClangDeclId clang_decl_id)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "llvm/Support/VirtualFileSystem.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/diagnostic_helpers.h"
|
||||
#include "toolchain/check/operator.h"
|
||||
#include "toolchain/diagnostics/diagnostic_emitter.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
@@ -31,6 +30,12 @@ auto ImportCppFiles(Context& context,
|
||||
auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
clang::FunctionDecl* clang_decl) -> SemIR::InstId;
|
||||
|
||||
// Imports an overloaded function set from Clang to Carbon.
|
||||
auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
|
||||
SemIR::NameId name_id,
|
||||
const clang::UnresolvedSet<4>& overload_set)
|
||||
-> SemIR::InstId;
|
||||
|
||||
// Looks up the given name in the Clang AST generated when importing C++ code
|
||||
// and returns a lookup result. If using the injected class name (`X.X()`),
|
||||
// imports the class constructor as a function named as the class.
|
||||
@@ -38,12 +43,6 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
|
||||
-> SemIR::ScopeLookupResult;
|
||||
|
||||
// Looks up the given operator in the Clang AST generated when importing C++
|
||||
// code and returns a lookup result.
|
||||
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId scope_id, Operator op)
|
||||
-> SemIR::ScopeLookupResult;
|
||||
|
||||
// Given a Carbon class declaration that was imported from some kind of C++
|
||||
// declaration, such as a class or enum, attempt to import a corresponding class
|
||||
// definition. Returns true if nothing went wrong (whether or not a definition
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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 "toolchain/check/cpp/operators.h"
|
||||
|
||||
#include "clang/Sema/Overload.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/check/inst.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Maps Carbon operator interface and operator names to Clang operator kinds.
|
||||
static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
|
||||
llvm::StringLiteral interface_name,
|
||||
llvm::StringLiteral op_name)
|
||||
-> std::optional<clang::OverloadedOperatorKind> {
|
||||
// Unary operators.
|
||||
if (interface_name == "Destroy" || interface_name == "As" ||
|
||||
interface_name == "ImplicitAs") {
|
||||
// TODO: Support destructors and conversions.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Increment and Decrement.
|
||||
if (interface_name == "Inc") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PlusPlus;
|
||||
}
|
||||
if (interface_name == "Dec") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_MinusMinus;
|
||||
}
|
||||
|
||||
// Arithmetic.
|
||||
if (interface_name == "Negate") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Minus;
|
||||
}
|
||||
|
||||
// Binary operators.
|
||||
|
||||
// Arithmetic Operators.
|
||||
if (interface_name == "AddWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Plus;
|
||||
}
|
||||
if (interface_name == "SubWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Minus;
|
||||
}
|
||||
if (interface_name == "MulWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Star;
|
||||
}
|
||||
if (interface_name == "DivWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Slash;
|
||||
}
|
||||
if (interface_name == "ModWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Percent;
|
||||
}
|
||||
|
||||
// Bitwise Operators.
|
||||
if (interface_name == "BitAndWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Amp;
|
||||
}
|
||||
if (interface_name == "BitOrWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Pipe;
|
||||
}
|
||||
if (interface_name == "BitXorWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_Caret;
|
||||
}
|
||||
if (interface_name == "LeftShiftWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_LessLess;
|
||||
}
|
||||
if (interface_name == "RightShiftWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_GreaterGreater;
|
||||
}
|
||||
|
||||
// Compound Assignment Arithmetic Operators.
|
||||
if (interface_name == "AddAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PlusEqual;
|
||||
}
|
||||
if (interface_name == "SubAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_MinusEqual;
|
||||
}
|
||||
if (interface_name == "MulAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_StarEqual;
|
||||
}
|
||||
if (interface_name == "DivAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_SlashEqual;
|
||||
}
|
||||
if (interface_name == "ModAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PercentEqual;
|
||||
}
|
||||
|
||||
// Compound Assignment Bitwise Operators.
|
||||
if (interface_name == "BitAndAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_AmpEqual;
|
||||
}
|
||||
if (interface_name == "BitOrAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_PipeEqual;
|
||||
}
|
||||
if (interface_name == "BitXorAssignWith") {
|
||||
CARBON_CHECK(op_name == "Op");
|
||||
return clang::OO_CaretEqual;
|
||||
}
|
||||
// TODO: Add support for `LeftShiftAssignWith` (`OO_LessLessEqual`) and
|
||||
// `RightShiftAssignWith` (`OO_GreaterGreaterEqual`) when references are
|
||||
// supported.
|
||||
|
||||
// Relational Operators.
|
||||
if (interface_name == "EqWith") {
|
||||
if (op_name == "Equal") {
|
||||
return clang::OO_EqualEqual;
|
||||
}
|
||||
CARBON_CHECK(op_name == "NotEqual");
|
||||
return clang::OO_ExclaimEqual;
|
||||
}
|
||||
if (interface_name == "OrderedWith") {
|
||||
if (op_name == "Less") {
|
||||
return clang::OO_Less;
|
||||
}
|
||||
if (op_name == "Greater") {
|
||||
return clang::OO_Greater;
|
||||
}
|
||||
if (op_name == "LessOrEquivalent") {
|
||||
return clang::OO_LessEqual;
|
||||
}
|
||||
CARBON_CHECK(op_name == "GreaterOrEquivalent");
|
||||
return clang::OO_GreaterEqual;
|
||||
}
|
||||
|
||||
context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
|
||||
interface_name));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId {
|
||||
Diagnostics::AnnotationScope annotate_diagnostics(
|
||||
&context.emitter(), [&](auto& builder) {
|
||||
CARBON_DIAGNOSTIC(InCppOperatorLookup, Note,
|
||||
"in `Cpp` operator `{0}` lookup", std::string);
|
||||
builder.Note(loc_id, InCppOperatorLookup, op.interface_name.str());
|
||||
});
|
||||
|
||||
auto op_kind =
|
||||
GetClangOperatorKind(context, loc_id, op.interface_name, op.op_name);
|
||||
if (!op_kind) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
auto arg_exprs = InventClangArgs(context, arg_ids);
|
||||
if (!arg_exprs.has_value()) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
clang::Sema& sema = context.sem_ir().clang_ast_unit()->getSema();
|
||||
|
||||
clang::UnresolvedSet<4> functions;
|
||||
// TODO: Add location accordingly.
|
||||
clang::OverloadCandidateSet candidate_set(
|
||||
clang::SourceLocation(), clang::OverloadCandidateSet::CSK_Operator);
|
||||
// This works for both unary and binary operators.
|
||||
sema.LookupOverloadedBinOp(candidate_set, *op_kind, functions, *arg_exprs);
|
||||
|
||||
for (auto& it : candidate_set) {
|
||||
if (!it.Function) {
|
||||
continue;
|
||||
}
|
||||
functions.addDecl(it.Function, it.FoundDecl.getAccess());
|
||||
}
|
||||
|
||||
return ImportCppOverloadSet(context, SemIR::NameScopeId::None,
|
||||
SemIR::NameId::CppOperator, functions);
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_
|
||||
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/operator.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Looks up the given operator in the Clang AST generated when importing C++
|
||||
// code using argument dependent lookup (ADL) and return overload set
|
||||
// instruction.
|
||||
auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_
|
||||
@@ -8,63 +8,11 @@
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/sem_ir/expr_info.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Invents a Clang argument expression to use in overload resolution to
|
||||
// represent the given Carbon argument instruction.
|
||||
static auto InventClangArg(Context& context, SemIR::InstId arg_id)
|
||||
-> clang::Expr* {
|
||||
clang::ExprValueKind value_kind;
|
||||
switch (SemIR::GetExprCategory(context.sem_ir(), arg_id)) {
|
||||
case SemIR::ExprCategory::NotExpr:
|
||||
CARBON_FATAL("Should not see these here");
|
||||
|
||||
case SemIR::ExprCategory::Error:
|
||||
return nullptr;
|
||||
|
||||
case SemIR::ExprCategory::DurableRef:
|
||||
value_kind = clang::ExprValueKind::VK_LValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::EphemeralRef:
|
||||
value_kind = clang::ExprValueKind::VK_XValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Value:
|
||||
case SemIR::ExprCategory::Initializing:
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Mixed:
|
||||
// TODO: Handle this by creating an InitListExpr.
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.insts().Get(arg_id).type_id() == SemIR::ErrorInst::TypeId) {
|
||||
// The argument error has already been diagnosed.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
|
||||
if (arg_cpp_type.isNull()) {
|
||||
CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
|
||||
"call argument of type {0} is not supported",
|
||||
TypeOfInstId);
|
||||
context.emitter().Emit(arg_id, CppCallArgTypeNotSupported, arg_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: Avoid heap allocating more of these on every call. Either cache them
|
||||
// somewhere or put them on the stack.
|
||||
return new (context.ast_context()) clang::OpaqueValueExpr(
|
||||
// TODO: Add location accordingly.
|
||||
clang::SourceLocation(), arg_cpp_type.getNonReferenceType(), value_kind);
|
||||
}
|
||||
|
||||
// Adds the given overload candidates to the candidate set.
|
||||
static auto AddOverloadCandidataes(clang::Sema& sema,
|
||||
clang::OverloadCandidateSet& candidate_set,
|
||||
@@ -134,14 +82,9 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
}
|
||||
llvm::SmallVector<clang::Expr*> arg_exprs;
|
||||
arg_exprs.reserve(arg_ids.size());
|
||||
for (SemIR::InstId arg_id : arg_ids) {
|
||||
auto* arg_expr = InventClangArg(context, arg_id);
|
||||
if (!arg_expr) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
arg_exprs.push_back(arg_expr);
|
||||
auto arg_exprs = InventClangArgs(context, arg_ids);
|
||||
if (!arg_exprs.has_value()) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
const SemIR::CppOverloadSet& overload_set =
|
||||
@@ -158,7 +101,7 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
|
||||
clang::Sema& sema = ast->getSema();
|
||||
|
||||
AddOverloadCandidataes(sema, candidate_set, overload_set.candidate_functions,
|
||||
self_expr, arg_exprs);
|
||||
self_expr, *arg_exprs);
|
||||
|
||||
// Find best viable function among the candidates.
|
||||
clang::OverloadCandidateSet::iterator best_viable_fn;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/literal.h"
|
||||
#include "toolchain/sem_ir/class.h"
|
||||
#include "toolchain/sem_ir/expr_info.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/inst.h"
|
||||
#include "toolchain/sem_ir/type.h"
|
||||
@@ -177,8 +178,12 @@ static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
|
||||
}
|
||||
}
|
||||
|
||||
// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
|
||||
// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
|
||||
// succeeds, or `clang::QualType::isNull()` if the type is not supported.
|
||||
// TODO: unify this with the C++ to Carbon type mapping function.
|
||||
auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType {
|
||||
static auto MapToCppType(Context& context, SemIR::InstId inst_id)
|
||||
-> clang::QualType {
|
||||
auto type_id = context.insts().Get(inst_id).type_id();
|
||||
llvm::SmallVector<SemIR::TypeId> wrapper_types;
|
||||
while (true) {
|
||||
@@ -223,4 +228,67 @@ auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType {
|
||||
return mapped_type;
|
||||
}
|
||||
|
||||
auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr* {
|
||||
clang::ExprValueKind value_kind;
|
||||
switch (SemIR::GetExprCategory(context.sem_ir(), arg_id)) {
|
||||
case SemIR::ExprCategory::NotExpr:
|
||||
CARBON_FATAL("Should not see these here");
|
||||
|
||||
case SemIR::ExprCategory::Error:
|
||||
return nullptr;
|
||||
|
||||
case SemIR::ExprCategory::DurableRef:
|
||||
value_kind = clang::ExprValueKind::VK_LValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::EphemeralRef:
|
||||
value_kind = clang::ExprValueKind::VK_XValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Value:
|
||||
case SemIR::ExprCategory::Initializing:
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Mixed:
|
||||
// TODO: Handle this by creating an InitListExpr.
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.insts().Get(arg_id).type_id() == SemIR::ErrorInst::TypeId) {
|
||||
// The argument error has already been diagnosed.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
|
||||
if (arg_cpp_type.isNull()) {
|
||||
CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
|
||||
"call argument of type {0} is not supported",
|
||||
TypeOfInstId);
|
||||
context.emitter().Emit(arg_id, CppCallArgTypeNotSupported, arg_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: Avoid heap allocating more of these on every call. Either cache them
|
||||
// somewhere or put them on the stack.
|
||||
return new (context.ast_context()) clang::OpaqueValueExpr(
|
||||
// TODO: Add location accordingly.
|
||||
clang::SourceLocation(), arg_cpp_type.getNonReferenceType(), value_kind);
|
||||
}
|
||||
|
||||
auto InventClangArgs(Context& context, llvm::ArrayRef<SemIR::InstId> arg_ids)
|
||||
-> std::optional<llvm::SmallVector<clang::Expr*>> {
|
||||
llvm::SmallVector<clang::Expr*> arg_exprs;
|
||||
arg_exprs.reserve(arg_ids.size());
|
||||
for (SemIR::InstId arg_id : arg_ids) {
|
||||
auto* arg_expr = InventClangArg(context, arg_id);
|
||||
if (!arg_expr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
arg_exprs.push_back(arg_expr);
|
||||
}
|
||||
return arg_exprs;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
@@ -11,10 +11,15 @@
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
|
||||
// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
|
||||
// succeeds, or `clang::QualType::isNull()` if the type is not supported.
|
||||
auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType;
|
||||
// Invents a Clang argument expression to use in overload resolution to
|
||||
// represent the given Carbon argument instruction.
|
||||
auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr*;
|
||||
|
||||
// For each arg, invents a Clang argument expression to use in overload
|
||||
// resolution or argument dependent lookup (ADL) to represent the given Carbon
|
||||
// argument instructions. Returns std::nullopt if any arg failed.
|
||||
auto InventClangArgs(Context& context, llvm::ArrayRef<SemIR::InstId> arg_ids)
|
||||
-> std::optional<llvm::SmallVector<clang::Expr*>>;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "toolchain/check/call.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/operators.h"
|
||||
#include "toolchain/check/generic.h"
|
||||
#include "toolchain/check/member_access.h"
|
||||
#include "toolchain/check/name_lookup.h"
|
||||
@@ -39,35 +39,18 @@ static auto GetOperatorOpFunction(Context& context, SemIR::LocId loc_id,
|
||||
op_name_id);
|
||||
}
|
||||
|
||||
// If the instruction is a C++ class, returns its parent scope id. Otherwise
|
||||
// returns `std::nullopt`.
|
||||
static auto GetCppClassTypeParentScope(Context& context, SemIR::InstId inst_id)
|
||||
-> std::optional<SemIR::NameScopeId> {
|
||||
// Returns whether the instruction is a C++ class.
|
||||
static auto IsCppClassType(Context& context, SemIR::InstId inst_id) -> bool {
|
||||
auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
|
||||
context.types().GetInstId(context.insts().Get(inst_id).type_id()));
|
||||
if (!class_type) {
|
||||
// Not a class.
|
||||
return std::nullopt;
|
||||
return false;
|
||||
}
|
||||
|
||||
const SemIR::Class& class_info = context.classes().Get(class_type->class_id);
|
||||
if (!class_info.is_complete() ||
|
||||
!context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) {
|
||||
// Not a C++ class.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SemIR::NameScopeId parent_scope_id = class_info.parent_scope_id;
|
||||
do {
|
||||
SemIR::NameScope& scope = context.name_scopes().Get(parent_scope_id);
|
||||
if (context.insts().Is<SemIR::Namespace>(scope.inst_id())) {
|
||||
break;
|
||||
}
|
||||
parent_scope_id = scope.parent_scope_id();
|
||||
|
||||
} while (parent_scope_id.has_value());
|
||||
|
||||
return parent_scope_id;
|
||||
return class_info.is_complete() &&
|
||||
context.name_scopes().Get(class_info.scope_id).is_cpp_scope();
|
||||
}
|
||||
|
||||
auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
@@ -78,16 +61,11 @@ auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
// the C++ operator.
|
||||
// TODO: Change impl lookup instead. See
|
||||
// https://github.com/carbon-language/carbon-lang/blob/db0a00d713015436844c55e7ac190a0f95556499/toolchain/check/operator.cpp#L76
|
||||
// TODO: We should do ADL-only lookup for operators
|
||||
// (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
|
||||
// into C++ types.
|
||||
auto cpp_parent_scope_id = GetCppClassTypeParentScope(context, operand_id);
|
||||
if (cpp_parent_scope_id) {
|
||||
SemIR::ScopeLookupResult cpp_lookup_result =
|
||||
ImportOperatorFromCpp(context, loc_id, *cpp_parent_scope_id, op);
|
||||
if (cpp_lookup_result.is_found()) {
|
||||
return PerformCall(context, loc_id, cpp_lookup_result.target_inst_id(),
|
||||
{operand_id});
|
||||
if (IsCppClassType(context, operand_id)) {
|
||||
SemIR::InstId cpp_inst_id =
|
||||
LookupCppOperator(context, loc_id, op, {operand_id});
|
||||
if (cpp_inst_id.has_value() && cpp_inst_id != SemIR::ErrorInst::InstId) {
|
||||
return PerformCall(context, loc_id, cpp_inst_id, {operand_id});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,22 +95,11 @@ auto BuildBinaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308666348
|
||||
// and
|
||||
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308664536
|
||||
// TODO: We should do ADL-only lookup for operators
|
||||
// (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
|
||||
// into C++ types.
|
||||
llvm::SmallVector<SemIR::NameScopeId, 2> cpp_operand_parent_scope_ids;
|
||||
for (SemIR::InstId operand_id : {lhs_id, rhs_id}) {
|
||||
auto cpp_parent_scope_id = GetCppClassTypeParentScope(context, operand_id);
|
||||
if (!cpp_parent_scope_id || llvm::is_contained(cpp_operand_parent_scope_ids,
|
||||
*cpp_parent_scope_id)) {
|
||||
continue;
|
||||
}
|
||||
cpp_operand_parent_scope_ids.push_back(*cpp_parent_scope_id);
|
||||
SemIR::ScopeLookupResult cpp_lookup_result =
|
||||
ImportOperatorFromCpp(context, loc_id, *cpp_parent_scope_id, op);
|
||||
if (cpp_lookup_result.is_found()) {
|
||||
return PerformCall(context, loc_id, cpp_lookup_result.target_inst_id(),
|
||||
{lhs_id, rhs_id});
|
||||
if (IsCppClassType(context, lhs_id) || IsCppClassType(context, rhs_id)) {
|
||||
SemIR::InstId cpp_inst_id =
|
||||
LookupCppOperator(context, loc_id, op, {lhs_id, rhs_id});
|
||||
if (cpp_inst_id.has_value() && cpp_inst_id != SemIR::ErrorInst::InstId) {
|
||||
return PerformCall(context, loc_id, cpp_inst_id, {lhs_id, rhs_id});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+113
-77
@@ -95,16 +95,20 @@ import Cpp library "postfix_inc_and_dec.h";
|
||||
fn F() {
|
||||
let postfix: Cpp.Postfix = Cpp.Postfix.Postfix();
|
||||
// TODO: Make the error more descriptive to clarify we call prefix operators of a class with suffix operators.
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+5]]:3: error: 1 argument passed to function expecting 2 arguments [CallArgCountMismatch]
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+7]]:3: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: ++postfix;
|
||||
// CHECK:STDERR: ^~~~~~~~~
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+4]]:3: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: ++postfix;
|
||||
// CHECK:STDERR: ^~~~~~~~~
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon: note: calling function declared here [InCallToEntity]
|
||||
// CHECK:STDERR:
|
||||
++postfix;
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+5]]:3: error: 1 argument passed to function expecting 2 arguments [CallArgCountMismatch]
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+7]]:3: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: --postfix;
|
||||
// CHECK:STDERR: ^~~~~~~~~
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+4]]:3: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: --postfix;
|
||||
// CHECK:STDERR: ^~~~~~~~~
|
||||
// CHECK:STDERR: fail_postfix_calling_prefix.carbon: note: calling function declared here [InCallToEntity]
|
||||
// CHECK:STDERR:
|
||||
--postfix;
|
||||
}
|
||||
@@ -225,22 +229,20 @@ import Cpp library "binary_operators.h";
|
||||
|
||||
fn F() {
|
||||
let c1: Cpp.C = Cpp.C.C();
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+8]]:24: error: cannot implicitly convert expression of type `Core.IntLiteral` to `Cpp.C` [ConversionFailure]
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: let c2: Cpp.C = c1 + 5;
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+5]]:24: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(Cpp.C)` [MissingImplInMemberAccessNote]
|
||||
// CHECK:STDERR: ^~~~~~
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: let c2: Cpp.C = c1 + 5;
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon: note: initializing function parameter [InCallToFunctionParam]
|
||||
// CHECK:STDERR: ^~~~~~
|
||||
// CHECK:STDERR:
|
||||
let c2: Cpp.C = c1 + 5;
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+8]]:19: error: cannot implicitly convert expression of type `Core.IntLiteral` to `Cpp.C` [ConversionFailure]
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: let c3: Cpp.C = 6 + c1;
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+5]]:19: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(Cpp.C)` [MissingImplInMemberAccessNote]
|
||||
// CHECK:STDERR: ^~~~~~
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: let c3: Cpp.C = 6 + c1;
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR: fail_call_with_wrong_type.carbon: note: initializing function parameter [InCallToFunctionParam]
|
||||
// CHECK:STDERR: ^~~~~~
|
||||
// CHECK:STDERR:
|
||||
let c3: Cpp.C = 6 + c1;
|
||||
}
|
||||
@@ -600,7 +602,10 @@ fn F() {
|
||||
//@dump-sem-ir-begin
|
||||
let c1: Cpp.N.C = Cpp.N.C.C();
|
||||
let c2: Cpp.N.C = Cpp.N.C.C();
|
||||
// CHECK:STDERR: fail_todo_import_operands_in_namespace_operator_in_global.carbon:[[@LINE+4]]:21: error: cannot access member of interface `Core.AddWith(Cpp.N.C)` in type `Cpp.N.C` that does not implement that interface [MissingImplInMemberAccess]
|
||||
// CHECK:STDERR: fail_todo_import_operands_in_namespace_operator_in_global.carbon:[[@LINE+7]]:21: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: let c3: Cpp.N.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR: fail_todo_import_operands_in_namespace_operator_in_global.carbon:[[@LINE+4]]:21: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: let c3: Cpp.N.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR:
|
||||
@@ -672,7 +677,7 @@ class C {
|
||||
auto operator+(C rhs) -> C;
|
||||
};
|
||||
|
||||
// --- fail_todo_import_member__add_with.carbon
|
||||
// --- fail_todo_import_member_add_with.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
@@ -682,7 +687,10 @@ fn F() {
|
||||
//@dump-sem-ir-begin
|
||||
let c1: Cpp.C = Cpp.C.C();
|
||||
let c2: Cpp.C = Cpp.C.C();
|
||||
// CHECK:STDERR: fail_todo_import_member__add_with.carbon:[[@LINE+4]]:19: error: cannot access member of interface `Core.AddWith(Cpp.C)` in type `Cpp.C` that does not implement that interface [MissingImplInMemberAccess]
|
||||
// CHECK:STDERR: fail_todo_import_member_add_with.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: let c3: Cpp.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR: fail_todo_import_member_add_with.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: let c3: Cpp.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR:
|
||||
@@ -737,13 +745,48 @@ import Cpp library "not_found.h";
|
||||
fn F() {
|
||||
let c1: Cpp.C = Cpp.C.C();
|
||||
let c2: Cpp.C = Cpp.C.C();
|
||||
// CHECK:STDERR: fail_import_not_found.carbon:[[@LINE+4]]:19: error: cannot access member of interface `Core.AddWith(Cpp.C)` in type `Cpp.C` that does not implement that interface [MissingImplInMemberAccess]
|
||||
// CHECK:STDERR: fail_import_not_found.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
|
||||
// CHECK:STDERR: let c3: Cpp.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR: fail_import_not_found.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
|
||||
// CHECK:STDERR: let c3: Cpp.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR:
|
||||
let c3: Cpp.C = c1 + c2;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Incomplete operand
|
||||
// ============================================================================
|
||||
|
||||
// --- incomplete.h
|
||||
|
||||
class Incomplete;
|
||||
class Complete {};
|
||||
|
||||
auto operator+(Complete lhs, Incomplete rhs) -> Complete;
|
||||
|
||||
auto foo(Complete complete) -> void;
|
||||
|
||||
// --- fail_import_incomplete.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
import Cpp library "incomplete.h";
|
||||
|
||||
fn F() {
|
||||
var c1: Cpp.Complete = Cpp.Complete.Complete();
|
||||
// CHECK:STDERR: fail_import_incomplete.carbon:[[@LINE+8]]:40: error: invalid use of incomplete type `Cpp.Incomplete` [IncompleteTypeInConversion]
|
||||
// CHECK:STDERR: let c3: Cpp.Complete = Cpp.foo(c1 + ({} as Cpp.Incomplete));
|
||||
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
|
||||
// CHECK:STDERR: fail_import_incomplete.carbon:[[@LINE-7]]:10: in file included here [InCppInclude]
|
||||
// CHECK:STDERR: ./incomplete.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
|
||||
// CHECK:STDERR: class Incomplete;
|
||||
// CHECK:STDERR: ^
|
||||
// CHECK:STDERR:
|
||||
let c3: Cpp.Complete = Cpp.foo(c1 + ({} as Cpp.Incomplete));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Operator overloading
|
||||
// ============================================================================
|
||||
@@ -755,7 +798,7 @@ auto operator+(C lhs, C rhs) -> C;
|
||||
class D {};
|
||||
auto operator+(D lhs, D rhs) -> D;
|
||||
|
||||
// --- fail_todo_import_overloading.carbon
|
||||
// --- import_overloading.carbon
|
||||
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
@@ -764,13 +807,6 @@ import Cpp library "overloading.h";
|
||||
fn F() {
|
||||
let c1: Cpp.C = Cpp.C.C();
|
||||
let c2: Cpp.C = Cpp.C.C();
|
||||
// CHECK:STDERR: fail_todo_import_overloading.carbon:[[@LINE+7]]:19: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 3` [SemanticsTodo]
|
||||
// CHECK:STDERR: let c3: Cpp.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR: fail_todo_import_overloading.carbon:[[@LINE+4]]:19: note: in `Cpp` operator `AddWith` lookup [InCppOperatorLookup]
|
||||
// CHECK:STDERR: let c3: Cpp.C = c1 + c2;
|
||||
// CHECK:STDERR: ^~~~~~~
|
||||
// CHECK:STDERR:
|
||||
let c3: Cpp.C = c1 + c2;
|
||||
}
|
||||
|
||||
@@ -780,8 +816,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.217: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @operator++__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.e73: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -803,7 +839,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @operator++__carbon_thunk [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -833,7 +869,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc8_21: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_23: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_23: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc8_26.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_26.1: %ptr.d9e = addr_of %.loc8_26.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_26.1)
|
||||
@@ -915,8 +951,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.217: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @Copy.Op [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.e73: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -1005,7 +1041,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @Copy.Op [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -1144,7 +1180,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %c1.var: ref %C = var %c1.var_patt
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc8_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc8_3.1: ref %C = splice_block %c1.var {}
|
||||
// CHECK:STDOUT: %addr.loc8_27: %ptr.d9e = addr_of %.loc8_3.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27)
|
||||
@@ -1162,7 +1198,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %c2.var: ref %C = var %c2.var_patt
|
||||
// CHECK:STDOUT: %Cpp.ref.loc9_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc9_3.1: ref %C = splice_block %c2.var {}
|
||||
// CHECK:STDOUT: %addr.loc9_27: %ptr.d9e = addr_of %.loc9_3.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27)
|
||||
@@ -1792,8 +1828,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.217: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @operator+__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.e73: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -1811,7 +1847,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @operator+__carbon_thunk [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -1831,7 +1867,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc8_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc8_27.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_27.1: %ptr.d9e = addr_of %.loc8_27.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27.1)
|
||||
@@ -1848,7 +1884,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Cpp.ref.loc9_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc9_27.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc9_27.1: %ptr.d9e = addr_of %.loc9_27.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27.1)
|
||||
@@ -1964,8 +2000,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.69f: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.169: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.169 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.169: type = cpp_overload_set_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.41c: %.169 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.838: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -1987,7 +2023,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.c52: %.169 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.c52: %.169 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.41c]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2008,7 +2044,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %N.ref.loc8_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %C.ref.loc8_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
|
||||
// CHECK:STDOUT: %.loc8_31.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_31.1: %ptr.838 = addr_of %.loc8_31.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_31.1)
|
||||
@@ -2027,7 +2063,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %N.ref.loc9_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %C.ref.loc9_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
|
||||
// CHECK:STDOUT: %.loc9_31.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc9_31.1: %ptr.838 = addr_of %.loc9_31.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_31.1)
|
||||
@@ -2091,14 +2127,14 @@ fn F() {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C1: type = class_type @C1 [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.20f: type = pattern_type %C1 [concrete]
|
||||
// CHECK:STDOUT: %.eb7: type = cpp_overload_set_type @C1__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %.eb7: type = cpp_overload_set_type @C2__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.56f: %.eb7 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.087: type = ptr_type %C1 [concrete]
|
||||
// CHECK:STDOUT: %C1__carbon_thunk.type: type = fn_type @C1__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C1__carbon_thunk: %C1__carbon_thunk.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %C2: type = class_type @C2 [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.846: type = pattern_type %C2 [concrete]
|
||||
// CHECK:STDOUT: %.74f: type = cpp_overload_set_type @C2.C2 [concrete]
|
||||
// CHECK:STDOUT: %.74f: type = cpp_overload_set_type @cpp_operator.1 [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.c81: %.74f = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.51f: type = ptr_type %C2 [concrete]
|
||||
// CHECK:STDOUT: %C2__carbon_thunk.type: type = fn_type @C2__carbon_thunk [concrete]
|
||||
@@ -2127,7 +2163,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C1.decl: type = class_decl @C1 [concrete = constants.%C1] {} {}
|
||||
// CHECK:STDOUT: %.91f: %.eb7 = cpp_overload_set_value @C1__carbon_thunk [concrete = constants.%empty_struct.56f]
|
||||
// CHECK:STDOUT: %.91f: %.eb7 = cpp_overload_set_value @C2__carbon_thunk [concrete = constants.%empty_struct.56f]
|
||||
// CHECK:STDOUT: %C1__carbon_thunk.decl: %C1__carbon_thunk.type = fn_decl @C1__carbon_thunk [concrete = constants.%C1__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2138,7 +2174,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C2.decl: type = class_decl @C2 [concrete = constants.%C2] {} {}
|
||||
// CHECK:STDOUT: %.ed5: %.74f = cpp_overload_set_value @C2.C2 [concrete = constants.%empty_struct.c81]
|
||||
// CHECK:STDOUT: %.ed5: %.74f = cpp_overload_set_value @cpp_operator.1 [concrete = constants.%empty_struct.c81]
|
||||
// CHECK:STDOUT: %C2__carbon_thunk.decl: %C2__carbon_thunk.type = fn_decl @C2__carbon_thunk [concrete = constants.%C2__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2275,8 +2311,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.69f: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.169: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.169 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.169: type = cpp_overload_set_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.41c: %.169 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.838: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -2296,7 +2332,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.c52: %.169 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.c52: %.169 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.41c]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2312,7 +2348,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %N.ref.loc8_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %C.ref.loc8_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
|
||||
// CHECK:STDOUT: %.loc8_31.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_31.1: %ptr.838 = addr_of %.loc8_31.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_31.1)
|
||||
@@ -2331,7 +2367,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %N.ref.loc9_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %C.ref.loc9_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
|
||||
// CHECK:STDOUT: %.loc9_31.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc9_31.1: %ptr.838 = addr_of %.loc9_31.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_31.1)
|
||||
@@ -2349,10 +2385,10 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %c1.ref: %C = name_ref c1, %c1
|
||||
// CHECK:STDOUT: %c2.ref: %C = name_ref c2, %c2
|
||||
// CHECK:STDOUT: %.loc14: type = splice_block %C.ref.loc14 [concrete = constants.%C] {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %N.ref.loc14: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %C.ref.loc14: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %.loc17: type = splice_block %C.ref.loc17 [concrete = constants.%C] {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %N.ref.loc17: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %C.ref.loc17: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %c3: %C = bind_name c3, <error> [concrete = <error>]
|
||||
// CHECK:STDOUT: %facet_value.loc9: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value]
|
||||
@@ -2379,8 +2415,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %O: type = class_type @O [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.b28: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.d80: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.d80 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.d80: type = cpp_overload_set_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.06f: %.d80 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.de2: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -2399,7 +2435,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %O.decl: type = class_decl @O [concrete = constants.%O] {} {}
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.7a9: %.d80 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.7a9: %.d80 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.06f]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2420,7 +2456,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %O.ref.loc8_24: type = name_ref O, imports.%O.decl [concrete = constants.%O]
|
||||
// CHECK:STDOUT: %C.ref.loc8_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct.06f]
|
||||
// CHECK:STDOUT: %.loc8_31.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_31.1: %ptr.de2 = addr_of %.loc8_31.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_31.1)
|
||||
@@ -2439,7 +2475,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %O.ref.loc9_24: type = name_ref O, imports.%O.decl [concrete = constants.%O]
|
||||
// CHECK:STDOUT: %C.ref.loc9_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct.06f]
|
||||
// CHECK:STDOUT: %.loc9_31.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc9_31.1: %ptr.de2 = addr_of %.loc9_31.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_31.1)
|
||||
@@ -2504,8 +2540,8 @@ fn F() {
|
||||
// CHECK:STDOUT: %O: type = class_type @O [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.84b: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.62f: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.62f = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.62f: type = cpp_overload_set_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.e94: %.62f = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.4b2: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -2528,7 +2564,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %O.decl: type = class_decl @O [concrete = constants.%O] {} {}
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.f56: %.62f = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.f56: %.62f = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.e94]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2550,7 +2586,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %N.ref.loc8_26: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %O.ref.loc8_28: type = name_ref O, imports.%O.decl [concrete = constants.%O]
|
||||
// CHECK:STDOUT: %C.ref.loc8_30: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct.e94]
|
||||
// CHECK:STDOUT: %.loc8_35.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_35.1: %ptr.4b2 = addr_of %.loc8_35.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_35.1)
|
||||
@@ -2571,7 +2607,7 @@ fn F() {
|
||||
// CHECK:STDOUT: %N.ref.loc9_26: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
|
||||
// CHECK:STDOUT: %O.ref.loc9_28: type = name_ref O, imports.%O.decl [concrete = constants.%O]
|
||||
// CHECK:STDOUT: %C.ref.loc9_30: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct.e94]
|
||||
// CHECK:STDOUT: %.loc9_35.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc9_35.1: %ptr.4b2 = addr_of %.loc9_35.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_35.1)
|
||||
@@ -2631,14 +2667,14 @@ fn F() {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- fail_todo_import_member__add_with.carbon
|
||||
// CHECK:STDOUT: --- fail_todo_import_member_add_with.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.217: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C.C [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %.d40: type = cpp_overload_set_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %empty_struct.e73: %.d40 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
|
||||
// CHECK:STDOUT: %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
|
||||
@@ -2654,7 +2690,7 @@ fn F() {
|
||||
// CHECK:STDOUT: import Cpp//...
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %.40b: %.d40 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: } {
|
||||
@@ -2669,7 +2705,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Cpp.ref.loc8_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc8_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc8_27.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc8_27.1: %ptr.d9e = addr_of %.loc8_27.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27.1)
|
||||
@@ -2686,7 +2722,7 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Cpp.ref.loc9_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
|
||||
// CHECK:STDOUT: %.loc9_27.1: ref %C = temporary_storage
|
||||
// CHECK:STDOUT: %addr.loc9_27.1: %ptr.d9e = addr_of %.loc9_27.1
|
||||
// CHECK:STDOUT: %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27.1)
|
||||
@@ -2703,9 +2739,9 @@ fn F() {
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %c1.ref: %C = name_ref c1, %c1
|
||||
// CHECK:STDOUT: %c2.ref: %C = name_ref c2, %c2
|
||||
// CHECK:STDOUT: %.loc14: type = splice_block %C.ref.loc14 [concrete = constants.%C] {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc14: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %.loc17: type = splice_block %C.ref.loc17 [concrete = constants.%C] {
|
||||
// CHECK:STDOUT: %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
|
||||
// CHECK:STDOUT: %C.ref.loc17: type = name_ref C, imports.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %c3: %C = bind_name c3, <error> [concrete = <error>]
|
||||
// CHECK:STDOUT: %facet_value.loc9: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value]
|
||||
|
||||
Reference in New Issue
Block a user