Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions common/crubit_feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ flagset::flags! {

/// Use ergonomic lifetime defaults when interpreting lifetime annotations.
AssumeLifetimes,

/// Enable formatting to Rust via C++.
Fmt,
}
}

Expand All @@ -55,6 +58,7 @@ impl CrubitFeature {
Self::Experimental => "experimental",
Self::CustomFfiTypes => "custom_ffi_types",
Self::AssumeLifetimes => "assume_lifetimes",
Self::Fmt => "fmt",
}
}

Expand All @@ -71,6 +75,7 @@ impl CrubitFeature {
Self::Experimental => "//features:experimental",
Self::CustomFfiTypes => "//features:custom_ffi_types",
Self::AssumeLifetimes => "//features:assume_lifetimes",
Self::Fmt => "//features:fmt",
}
}
}
Expand All @@ -88,6 +93,7 @@ pub fn named_features(name: &[u8]) -> Option<flagset::FlagSet<CrubitFeature>> {
b"experimental" => CrubitFeature::Experimental.into(),
b"custom_ffi_types" => CrubitFeature::CustomFfiTypes.into(),
b"assume_lifetimes" => CrubitFeature::AssumeLifetimes.into(),
b"fmt" => CrubitFeature::Fmt.into(),
_ => return None,
};
Some(features)
Expand Down Expand Up @@ -195,6 +201,7 @@ mod tests {
| CrubitFeature::NonUnpinCtor
| CrubitFeature::Experimental
| CrubitFeature::CustomFfiTypes
| CrubitFeature::Fmt
);
}

Expand Down Expand Up @@ -223,6 +230,7 @@ mod tests {
| CrubitFeature::NonUnpinCtor
| CrubitFeature::Experimental
| CrubitFeature::CustomFfiTypes
| CrubitFeature::Fmt
);
}

Expand All @@ -239,6 +247,7 @@ mod tests {
| CrubitFeature::NonUnpinCtor
| CrubitFeature::Experimental
| CrubitFeature::CustomFfiTypes
| CrubitFeature::Fmt
);
}
}
10 changes: 10 additions & 0 deletions features/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ crubit_feature_hint(
visibility = ["//visibility:public"],
)

# A feature set enabling formatting to Rust via C++.
#
# See crubit.rs-features#other
crubit_feature_hint(
name = "fmt",
compatible_with = ["//buildenv/target:non_prod"],
crubit_features = SUPPORTED_FEATURES + ["fmt"],
visibility = ["//visibility:public"],
)

# A feature set containing experimental Crubit features, in addition to the officially supported
# features.
#
Expand Down
2 changes: 2 additions & 0 deletions rs_bindings_from_cc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ cc_library(
"//rs_bindings_from_cc/importers:var",
"@abseil-cpp//absl/algorithm:container",
"@abseil-cpp//absl/base:no_destructor",
"@abseil-cpp//absl/base:nullability",
"@abseil-cpp//absl/container:flat_hash_map",
"@abseil-cpp//absl/container:flat_hash_set",
"@abseil-cpp//absl/log",
Expand Down Expand Up @@ -356,6 +357,7 @@ crubit_cc_test(
"//testing/base/public:gunit_main",
"@abseil-cpp//absl/functional:overload",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
],
)
Expand Down
22 changes: 22 additions & 0 deletions rs_bindings_from_cc/decl_importer.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "lifetime_annotations/type_lifetimes.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/ir.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/RawCommentList.h"
Expand Down Expand Up @@ -172,6 +173,27 @@ class ImportContext {
virtual bool AreAssumedLifetimesEnabledForTarget(
const BazelLabel& label) const = 0;

// Returns true iff `label` has opted in to formatter detection.
virtual bool IsFmtEnabledForTarget(const BazelLabel& label) const = 0;

// Returns whether the given `decl`'s type has a detectable formatter.
//
// For a type `T`, finds:
// * `template <typename Sink> void AbslStringify(Sink&, const T&)`
// * `template <typename Sink> void AbslStringify(Sink&, T)`
// * `std::ostream& operator<<(std::ostream&, const T&)`
// * `std::ostream& operator<<(std::ostream&, T)`
//
// in any of the following:
// * `T`'s namespace
// * `T`'s friends
// * `T`'s bases' friends
//
// and recurses on the bases of `T`. e.g., if `T` inherits from `B`, then
// include both `AbslStringify(Sink&, T)` and `AbslStringify(Sink&, B)` in
// `B`.
virtual bool DetectFormatter(const clang::TypeDecl& decl) const = 0;

// Gets an IR UnqualifiedIdentifier for the named decl.
//
// If the decl's name is an identifier, this returns that identifier as-is.
Expand Down
135 changes: 132 additions & 3 deletions rs_bindings_from_cc/importer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "absl/algorithm/container.h"
#include "absl/base/no_destructor.h"
#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
Expand Down Expand Up @@ -54,6 +55,7 @@
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/RawCommentList.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeBase.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileManager.h"
Expand All @@ -68,6 +70,7 @@
#include "llvm/Support/Casting.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"

namespace crubit {
namespace {
Expand Down Expand Up @@ -121,6 +124,79 @@ bool IsBuiltinFunction(const clang::Decl* decl) {
return function->getBuiltinID() != 0;
}

bool IsTypeValueOrRefToConst(clang::QualType candidate,
clang::CanQualType target) {
return candidate->getCanonicalTypeUnqualified() == target ||
(candidate->isLValueReferenceType() &&
candidate.getNonReferenceType().getCanonicalType() ==
target.withConst());
}

bool IsLvalueRefToUnqualified(clang::QualType type) {
return type->isLValueReferenceType() &&
!type.getNonReferenceType().getCanonicalType().hasQualifiers();
}

bool IsStdTemplate(const clang::TemplateDecl& decl, clang::StringRef name) {
const auto* class_template =
clang::dyn_cast<const clang::ClassTemplateDecl>(&decl);
return class_template != nullptr && class_template->getName() == name &&
class_template->isInStdNamespace();
}

bool IsCharTraitsChar(clang::QualType type) {
const clang::TemplateSpecializationType* specialization =
type->getAsNonAliasTemplateSpecializationType();
return specialization != nullptr &&
IsStdTemplate(*specialization->getTemplateName().getAsTemplateDecl(),
"char_traits") &&
specialization->template_arguments().size() == 1 &&
specialization->template_arguments()[0].getAsType()->isCharType();
}

bool IsOstreamRef(clang::QualType type) {
if (!IsLvalueRefToUnqualified(type)) {
return false;
}
const clang::TemplateSpecializationType* specialization =
type.getNonReferenceType()->getAsNonAliasTemplateSpecializationType();
return specialization != nullptr &&
IsStdTemplate(*specialization->getTemplateName().getAsTemplateDecl(),
"basic_ostream") &&
!specialization->template_arguments().empty() &&
specialization->template_arguments()[0].getAsType()->isCharType() &&
(specialization->template_arguments().size() == 1 ||
IsCharTraitsChar(
specialization->template_arguments()[1].getAsType()));
}

bool DetectFormatterFunction(const clang::Decl& decl,
clang::CanQualType formatted_type) {
if (const auto* function_template_decl =
clang::dyn_cast<const clang::FunctionTemplateDecl>(&decl);
function_template_decl != nullptr) {
return function_template_decl->getName() == "AbslStringify" &&
function_template_decl->getAsFunction()->getNumParams() == 2 &&
IsTypeValueOrRefToConst(
/*candidate=*/function_template_decl->getAsFunction()
->getParamDecl(1)
->getType(),
/*target=*/formatted_type);
}
if (const auto* function_decl =
clang::dyn_cast<const clang::FunctionDecl>(&decl);
function_decl != nullptr) {
return function_decl->getOverloadedOperator() == clang::OO_LessLess &&
function_decl->getNumParams() == 2 &&
IsOstreamRef(function_decl->getParamDecl(0)->getType()) &&
IsTypeValueOrRefToConst(
/*candidate=*/function_decl->getParamDecl(1)->getType(),
/*target=*/formatted_type) &&
IsOstreamRef(function_decl->getReturnType());
}
return false;
}

} // namespace

namespace {
Expand Down Expand Up @@ -893,11 +969,64 @@ bool Importer::IsFromProtoTarget(const clang::Decl& decl) const {
return filename.has_value() && filename->ends_with(".proto.h");
}

bool Importer::AreAssumedLifetimesEnabledForTarget(
const BazelLabel& label) const {
bool Importer::IsFeatureEnabledForTarget(const BazelLabel& label,
absl::string_view feature) const {
if (auto i = invocation_.ir_.crubit_features.find(label);
i != invocation_.ir_.crubit_features.end()) {
return i->second.contains("assume_lifetimes");
return i->second.contains(feature);
}
return false;
}

bool Importer::AreAssumedLifetimesEnabledForTarget(
const BazelLabel& label) const {
return IsFeatureEnabledForTarget(label, "assume_lifetimes");
}

bool Importer::IsFmtEnabledForTarget(const BazelLabel& label) const {
return IsFeatureEnabledForTarget(label, "fmt");
}

bool Importer::DetectFormatter(const clang::TypeDecl& decl) const {
clang::CanQualType type = ctx_.getCanonicalTypeDeclType(&decl);
return DetectFormatterForType(/*lookup=*/type, /*target=*/type);
}

bool Importer::DetectFormatterForType(clang::CanQualType lookup,
clang::CanQualType target) const {
if (const clang::CXXRecordDecl* record = lookup->getAsCXXRecordDecl();
record != nullptr) {
for (const clang::FriendDecl* absl_nonnull friend_decl :
record->friends()) {
const clang::NamedDecl* inner_friend = friend_decl->getFriendDecl();
if (inner_friend != nullptr &&
DetectFormatterFunction(*inner_friend, target)) {
return true;
}
}
for (const clang::CXXBaseSpecifier& base_specifier : record->bases()) {
clang::CanQualType base_type =
ctx_.getCanonicalType(base_specifier.getType());
if (DetectFormatterForType(/*lookup=*/base_type, /*target=*/target) ||
DetectFormatterForType(/*lookup=*/base_type, /*target=*/base_type)) {
return true;
}
}
}
const clang::Type* lookup_type = lookup.getTypePtrOrNull();
if (lookup_type == nullptr) {
return false;
}
const clang::TagDecl* lookup_decl = lookup_type->getAsTagDecl();
if (lookup_decl == nullptr) {
return false;
}
const clang::DeclContext* absl_nonnull context =
lookup_decl->getDeclContext()->getEnclosingNamespaceContext();
for (const clang::Decl* absl_nonnull decl : context->decls()) {
if (DetectFormatterFunction(*decl, target)) {
return true;
}
}
return false;
}
Expand Down
9 changes: 9 additions & 0 deletions rs_bindings_from_cc/importer.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "absl/log/check.h"
#include "absl/log/die_if_null.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "lifetime_annotations/type_lifetimes.h"
#include "rs_bindings_from_cc/bazel_types.h"
#include "rs_bindings_from_cc/decl_importer.h"
Expand Down Expand Up @@ -119,6 +120,8 @@ class Importer final : public ImportContext {
bool IsCrubitEnabledForTarget(const BazelLabel& label) const override;
bool AreAssumedLifetimesEnabledForTarget(
const BazelLabel& label) const override;
bool IsFmtEnabledForTarget(const BazelLabel& label) const override;
bool DetectFormatter(const clang::TypeDecl& decl) const override;
absl::StatusOr<TranslatedUnqualifiedIdentifier> GetTranslatedName(
const clang::NamedDecl* named_decl) const override;
absl::StatusOr<TranslatedIdentifier> GetTranslatedIdentifier(
Expand Down Expand Up @@ -189,6 +192,12 @@ class Importer final : public ImportContext {
CcType ConvertTemplateSpecializationType(
const clang::TemplateSpecializationType* type);

bool IsFeatureEnabledForTarget(const BazelLabel& label,
absl::string_view feature) const;

bool DetectFormatterForType(clang::CanQualType lookup,
clang::CanQualType target) const;

// The different decl importers. Note that order matters: the first importer
// to successfully match a decl "wins", and no other importers are tried.
std::vector<std::unique_ptr<DeclImporter>> decl_importers_;
Expand Down
Loading