diff --git a/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform.rs b/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform.rs index be692dd3d..460e815d6 100644 --- a/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform.rs +++ b/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform.rs @@ -41,9 +41,71 @@ fn lifetime_arity(db: &BindingsGenerator, ty: &CcType) -> Result { } } -fn record_lifetime_arity(db: &BindingsGenerator, rc: &Record) -> Result { - // 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 { + // 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, ¶m.type_); + } + } + } else if this_param.clang_lifetimebound { + return lifetime_arity(db, &f.return_type); + } + } + _ => (), + } + } + Ok(0) } fn decl_lifetime_arity_impl( @@ -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 { 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) } diff --git a/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform_test.rs b/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform_test.rs index c38149af6..84d352793 100644 --- a/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform_test.rs +++ b/rs_bindings_from_cc/generate_bindings/lifetime_defaults_transform_test.rs @@ -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; @@ -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 { + 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(()) +}