blob: 5560158547be920189a74f9d6d2d14fba9dae71c [file] [log] [blame]
Brian Silverman4e662aa2022-05-11 23:10:19 -07001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8use crate::{
9 conversion::analysis::fun::{
10 function_wrapper::RustConversionType, ArgumentAnalysis, ReceiverMutability,
11 },
12 types::QualifiedName,
13};
14use indexmap::set::IndexSet as HashSet;
15use proc_macro2::TokenStream;
16use quote::{quote, ToTokens};
17use std::borrow::Cow;
18use syn::{
19 parse_quote, punctuated::Punctuated, token::Comma, FnArg, GenericArgument, PatType, Path,
20 PathSegment, ReturnType, Type, TypePath, TypeReference,
21};
22
23/// Function which can add explicit lifetime parameters to function signatures
24/// where necessary, based on analysis of parameters and return types.
25/// This is necessary in three cases:
26/// 1) where the parameter is a Pin<&mut T>
27/// and the return type is some kind of reference - because lifetime elision
28/// is not smart enough to see inside a Pin.
29/// 2) as a workaround for https://github.com/dtolnay/cxx/issues/1024, where the
30/// input parameter is a non-POD type but the output reference is a POD or
31/// built-in type
32/// 3) Any parameter is any form of reference, and we're returning an `impl New`
33/// 3a) an 'impl ValueParam' counts as a reference.
34pub(crate) fn add_explicit_lifetime_if_necessary<'r>(
35 param_details: &[ArgumentAnalysis],
36 mut params: Punctuated<FnArg, Comma>,
37 ret_type: Cow<'r, ReturnType>,
38 non_pod_types: &HashSet<QualifiedName>,
Brian Silverman4e662aa2022-05-11 23:10:19 -070039) -> (
40 Option<TokenStream>,
41 Punctuated<FnArg, Comma>,
42 Cow<'r, ReturnType>,
43) {
44 let has_mutable_receiver = param_details.iter().any(|pd| {
45 matches!(pd.self_type, Some((_, ReceiverMutability::Mutable)))
46 && !pd.is_placement_return_destination
47 });
48
49 let any_param_is_reference = param_details.iter().any(|pd| {
50 pd.has_lifetime
51 || matches!(
52 pd.conversion.rust_conversion,
53 RustConversionType::FromValueParamToPtr
54 )
55 });
56 let return_type_is_impl = return_type_is_impl(&ret_type);
57 let non_pod_ref_param = reference_parameter_is_non_pod_reference(&params, non_pod_types);
58 let ret_type_pod = return_type_is_pod_or_known_type_reference(&ret_type, non_pod_types);
59 let returning_impl_with_a_reference_param = return_type_is_impl && any_param_is_reference;
60 let hits_1024_bug = non_pod_ref_param && ret_type_pod;
61 if !(has_mutable_receiver || hits_1024_bug || returning_impl_with_a_reference_param) {
62 return (None, params, ret_type);
63 }
64 let new_return_type = match ret_type.as_ref() {
65 ReturnType::Type(rarrow, boxed_type) => match boxed_type.as_ref() {
66 Type::Reference(rtr) => {
67 let mut new_rtr = rtr.clone();
68 new_rtr.lifetime = Some(parse_quote! { 'a });
69 Some(ReturnType::Type(
70 *rarrow,
71 Box::new(Type::Reference(new_rtr)),
72 ))
73 }
74 Type::Path(typ) => {
75 let mut new_path = typ.clone();
76 add_lifetime_to_pinned_reference(&mut new_path.path.segments)
77 .ok()
78 .map(|_| ReturnType::Type(*rarrow, Box::new(Type::Path(new_path))))
79 }
80 Type::ImplTrait(tyit) => {
81 let old_tyit = tyit.to_token_stream();
82 Some(parse_quote! {
83 #rarrow #old_tyit + 'a
84 })
85 }
86 _ => None,
87 },
88 _ => None,
89 };
90
91 match new_return_type {
92 None => (None, params, ret_type),
93 Some(new_return_type) => {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070094 for FnArg::Typed(PatType { ty, .. }) | FnArg::Receiver(syn::Receiver { ty, .. }) in
95 params.iter_mut()
96 {
97 match ty.as_mut() {
98 Type::Path(TypePath {
99 path: Path { segments, .. },
100 ..
101 }) => add_lifetime_to_pinned_reference(segments).unwrap_or(()),
102 Type::Reference(tyr) => add_lifetime_to_reference(tyr),
103 Type::ImplTrait(tyit) => add_lifetime_to_impl_trait(tyit),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700104 _ => {}
105 }
106 }
107
108 (Some(quote! { <'a> }), params, Cow::Owned(new_return_type))
109 }
110 }
111}
112
113fn reference_parameter_is_non_pod_reference(
114 params: &Punctuated<FnArg, Comma>,
115 non_pod_types: &HashSet<QualifiedName>,
116) -> bool {
117 params.iter().any(|param| match param {
118 FnArg::Typed(PatType { ty, .. }) => match ty.as_ref() {
119 Type::Reference(TypeReference { elem, .. }) => match elem.as_ref() {
120 Type::Path(typ) => {
121 let qn = QualifiedName::from_type_path(typ);
122 non_pod_types.contains(&qn)
123 }
124 _ => false,
125 },
126 _ => false,
127 },
128 _ => false,
129 })
130}
131
132fn return_type_is_pod_or_known_type_reference(
133 ret_type: &ReturnType,
134 non_pod_types: &HashSet<QualifiedName>,
135) -> bool {
136 match ret_type {
137 ReturnType::Type(_, boxed_type) => match boxed_type.as_ref() {
138 Type::Reference(rtr) => match rtr.elem.as_ref() {
139 Type::Path(typ) => {
140 let qn = QualifiedName::from_type_path(typ);
141 !non_pod_types.contains(&qn)
142 }
143 _ => false,
144 },
145 _ => false,
146 },
147 _ => false,
148 }
149}
150
151fn return_type_is_impl(ret_type: &ReturnType) -> bool {
152 matches!(ret_type, ReturnType::Type(_, boxed_type) if matches!(boxed_type.as_ref(), Type::ImplTrait(..)))
153}
154
155#[derive(Debug)]
156enum AddLifetimeError {
157 WasNotPin,
158}
159
160fn add_lifetime_to_pinned_reference(
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700161 segments: &mut Punctuated<PathSegment, syn::token::PathSep>,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700162) -> Result<(), AddLifetimeError> {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700163 static EXPECTED_SEGMENTS: &[(&[&str], bool)] = &[
164 (&["std", "core"], false),
165 (&["pin"], false),
166 (&["Pin"], true), // true = act on the arguments of this segment
Brian Silverman4e662aa2022-05-11 23:10:19 -0700167 ];
168
169 for (seg, (expected_name, act)) in segments.iter_mut().zip(EXPECTED_SEGMENTS.iter()) {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700170 if !expected_name
171 .iter()
172 .any(|expected_name| seg.ident == expected_name)
173 {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700174 return Err(AddLifetimeError::WasNotPin);
175 }
176 if *act {
177 match &mut seg.arguments {
178 syn::PathArguments::AngleBracketed(aba) => match aba.args.iter_mut().next() {
179 Some(GenericArgument::Type(Type::Reference(tyr))) => {
180 add_lifetime_to_reference(tyr);
181 }
182 _ => panic!("Expected generic args with a reference"),
183 },
184 _ => panic!("Expected angle bracketed args"),
185 }
186 }
187 }
188 Ok(())
189}
190
191fn add_lifetime_to_reference(tyr: &mut syn::TypeReference) {
192 tyr.lifetime = Some(parse_quote! { 'a })
193}
194
195fn add_lifetime_to_impl_trait(tyit: &mut syn::TypeImplTrait) {
196 tyit.bounds
197 .push(syn::TypeParamBound::Lifetime(parse_quote! { 'a }))
198}