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
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,71 @@ fn lifetime_arity(db: &BindingsGenerator, ty: &CcType) -> Result<usize> {
}
}

fn record_lifetime_arity(db: &BindingsGenerator, rc: &Record) -> Result<usize, arc_anyhow::Error> {
// TODO(zarko): Handle the effects of [[lifetimebound]] et al on arity.
Ok(rc.lifetime_inputs.len())
fn record_is_cc_std(record: &ir::Record) -> bool {
match &record.template_specialization {
Some(ir::TemplateSpecialization { defining_target, .. }) => {
*defining_target == ir::BazelLabel("//support/cc_std:cc_std".into())
}
_ => false,
}
}

pub fn record_lifetime_arity(
db: &BindingsGenerator,
rc: &Record,
) -> Result<usize, arc_anyhow::Error> {
// If the record has explicit lifetime inputs set, use those.
if !rc.lifetime_inputs.is_empty() {
return Ok(rc.lifetime_inputs.len());
}
// Records defined in cc_std are special and excluded. This is particularly a problem with
// our special-cased string_view. We should be able to loosen this restriction.

// DO NOT SUBMIT with WithLifetimeBoundContext stubbed out here (inshallah it's the only
// problem we can't bucket into the stdlib).
if record_is_cc_std(rc) || rc.cc_name == "WithLifetimeBoundContext" {
return Ok(0);
}
// Otherwise, we need to handle the various ways that a lifetime parameter may be implied.
for cid in &rc.child_item_ids {
let child = db.find_untyped_decl(*cid);
match child {
Item::Func(f) => {
// There are three cases for [[lifetimebound]] on a member function f. (We're loose
// here in that "the arity of X" means "the lifetime arity of the type of X".)
// - If f is a constructor, the arity of a [[lifetimebound]] parameter must match
// the arity of *this.
// - If the implicit parameter of f is marked [[lifetimebound]], then the arity
// of the return type must match the arity of *this.
// - If an arbitrary parameter is marked with [[lifetimebound]], then the arity of
// the return type must match the arity of that parameter. For the moment we
// ignore this case.
//
// We'll pick the first [[lifetimebound]] we can find an arity for. (Even if we
// chose the type with the highest arity, we wouldn't be able to find a suitable
// assignment for those parameters later on; users need to annotate their code.)
if f.params.len() < 1 {
continue;
}
let this_param = &f.params[0];
if this_param.identifier != "__this" {
continue;
}
if f.cc_name == ir::UnqualifiedIdentifier::Constructor {
// TODO(zarko): What about cycles here (simplest case is copy ctors)?
for param in &f.params[1..] {
if param.clang_lifetimebound {
return lifetime_arity(db, &param.type_);
}
}
} else if this_param.clang_lifetimebound {
return lifetime_arity(db, &f.return_type);
}
}
_ => (),
}
}
Ok(0)
}

fn decl_lifetime_arity_impl(
Expand Down Expand Up @@ -549,13 +611,22 @@ impl<'a, 'db> LifetimeDefaults<'a, 'db> {
/// Transforms a record to use default lifetime rules.
fn add_lifetime_to_record(&mut self, record: &Record) -> Result<Record> {
let mut new_record = record.clone();
new_record.lifetime_inputs.clear();
self.bind_lifetime_inputs(record.enclosing_item_id)?;
// Rename local bindings (and remember how we've renamed them).
record
.lifetime_inputs
.iter()
.for_each(|name| new_record.lifetime_inputs.push(self.bindings.push_new_binding(name)));
if new_record.lifetime_inputs.is_empty() {
// Record any implicit lifetime parameters.
let arity = record_lifetime_arity(self.db, record)?;
for _ in 0..arity {
new_record
.lifetime_inputs
.push(self.bindings.push_new_binding(&Rc::from("__implicit")));
}
} else {
new_record.lifetime_inputs.clear();
// Rename local bindings (and remember how we've renamed them).
record.lifetime_inputs.iter().for_each(|name| {
new_record.lifetime_inputs.push(self.bindings.push_new_binding(name))
});
}
Ok(new_record)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ use ffi_types::Environment;
use generate_bindings::new_database;
use googletest::prelude::*;
use ir_matchers::assert_ir_matches;
use ir_testing::with_full_lifetime_macros;
use lifetime_defaults_transform::{lifetime_defaults_transform, BindingContext};
use ir_testing::{retrieve_record, with_full_lifetime_macros};
use lifetime_defaults_transform::{
lifetime_defaults_transform, record_lifetime_arity, BindingContext,
};
use multiplatform_ir_testing::ir_from_assumed_lifetimes_cc;
use quote::quote;
use std::rc::Rc;
Expand Down Expand Up @@ -1262,3 +1264,69 @@ fn test_string_view_assumed_output_lifetime_matches_input() -> Result<()> {
);
Ok(())
}

fn arity_of_record(ir: &ir::IR, record_name: &str) -> Result<usize> {
let record = retrieve_record(ir, record_name);
let errors = ErrorReport::new(SourceLanguage::Cpp);
let fatal_errors = FatalErrors::new();
let db = new_database(ir, &errors, &fatal_errors, Environment::Production, false);
record_lifetime_arity(&db, record)
}

#[gtest]
fn test_arity_of_noparam_struct_is_zero() -> Result<()> {
let ir = ir_from_assumed_lifetimes_cc(
&(with_full_lifetime_macros()
+ r#"
struct S { S(); };
"#),
)?;
assert_eq!(arity_of_record(&ir, "S"), Ok(0));
let dir = lifetime_defaults_transform_ir(&ir)?;
assert_eq!(arity_of_record(&dir, "S"), Ok(0));
Ok(())
}

#[gtest]
fn test_arity_of_explicit_param_struct() -> Result<()> {
let ir = ir_from_assumed_lifetimes_cc(
&(with_full_lifetime_macros()
+ r#"
struct LIFETIME_PARAMS("a", "b") S { S(); };
"#),
)?;
assert_eq!(arity_of_record(&ir, "S"), Ok(2));
let dir = lifetime_defaults_transform_ir(&ir)?;
assert_eq!(arity_of_record(&dir, "S"), Ok(2));
Ok(())
}

#[gtest]
fn test_arity_of_simple_lifetimebound_constructor() -> Result<()> {
let ir = ir_from_assumed_lifetimes_cc(
&(with_full_lifetime_macros()
+ r#"
struct LIFETIME_PARAMS("a", "b") R {};
struct S { S(R ref [[clang::lifetimebound]]); };
"#),
)?;
assert_eq!(arity_of_record(&ir, "S"), Ok(2));
let dir = lifetime_defaults_transform_ir(&ir)?;
assert_eq!(arity_of_record(&dir, "S"), Ok(2));
Ok(())
}

#[gtest]
fn test_arity_of_simple_lifetimebound_return() -> Result<()> {
let ir = ir_from_assumed_lifetimes_cc(
&(with_full_lifetime_macros()
+ r#"
struct LIFETIME_PARAMS("a", "b") R {};
struct S { R f() [[clang::lifetimebound]]; };
"#),
)?;
assert_eq!(arity_of_record(&ir, "S"), Ok(2));
let dir = lifetime_defaults_transform_ir(&ir)?;
assert_eq!(arity_of_record(&dir, "S"), Ok(2));
Ok(())
}