blob: ea2c782b542b0c0c05a30aa91e73624b525b5334 [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>,
39 assert_all_parameters_are_references: bool,
40) -> (
41 Option<TokenStream>,
42 Punctuated<FnArg, Comma>,
43 Cow<'r, ReturnType>,
44) {
45 let has_mutable_receiver = param_details.iter().any(|pd| {
46 matches!(pd.self_type, Some((_, ReceiverMutability::Mutable)))
47 && !pd.is_placement_return_destination
48 });
49
50 let any_param_is_reference = param_details.iter().any(|pd| {
51 pd.has_lifetime
52 || matches!(
53 pd.conversion.rust_conversion,
54 RustConversionType::FromValueParamToPtr
55 )
56 });
57 let return_type_is_impl = return_type_is_impl(&ret_type);
58 let non_pod_ref_param = reference_parameter_is_non_pod_reference(&params, non_pod_types);
59 let ret_type_pod = return_type_is_pod_or_known_type_reference(&ret_type, non_pod_types);
60 let returning_impl_with_a_reference_param = return_type_is_impl && any_param_is_reference;
61 let hits_1024_bug = non_pod_ref_param && ret_type_pod;
62 if !(has_mutable_receiver || hits_1024_bug || returning_impl_with_a_reference_param) {
63 return (None, params, ret_type);
64 }
65 let new_return_type = match ret_type.as_ref() {
66 ReturnType::Type(rarrow, boxed_type) => match boxed_type.as_ref() {
67 Type::Reference(rtr) => {
68 let mut new_rtr = rtr.clone();
69 new_rtr.lifetime = Some(parse_quote! { 'a });
70 Some(ReturnType::Type(
71 *rarrow,
72 Box::new(Type::Reference(new_rtr)),
73 ))
74 }
75 Type::Path(typ) => {
76 let mut new_path = typ.clone();
77 add_lifetime_to_pinned_reference(&mut new_path.path.segments)
78 .ok()
79 .map(|_| ReturnType::Type(*rarrow, Box::new(Type::Path(new_path))))
80 }
81 Type::ImplTrait(tyit) => {
82 let old_tyit = tyit.to_token_stream();
83 Some(parse_quote! {
84 #rarrow #old_tyit + 'a
85 })
86 }
87 _ => None,
88 },
89 _ => None,
90 };
91
92 match new_return_type {
93 None => (None, params, ret_type),
94 Some(new_return_type) => {
95 for mut param in params.iter_mut() {
96 match &mut param {
97 FnArg::Typed(PatType { ty, .. }) => match ty.as_mut() {
98 Type::Path(TypePath {
99 path: Path { segments, .. },
100 ..
101 }) => add_lifetime_to_pinned_reference(segments).unwrap_or_else(|e| {
102 if assert_all_parameters_are_references {
103 panic!("Expected a pinned reference: {:?}", e)
104 }
105 }),
106 Type::Reference(tyr) => add_lifetime_to_reference(tyr),
107 Type::ImplTrait(tyit) => add_lifetime_to_impl_trait(tyit),
108 _ if assert_all_parameters_are_references => {
109 panic!("Expected Pin<&mut T> or &T")
110 }
111 _ => {}
112 },
113 _ if assert_all_parameters_are_references => panic!("Unexpected fnarg"),
114 _ => {}
115 }
116 }
117
118 (Some(quote! { <'a> }), params, Cow::Owned(new_return_type))
119 }
120 }
121}
122
123fn reference_parameter_is_non_pod_reference(
124 params: &Punctuated<FnArg, Comma>,
125 non_pod_types: &HashSet<QualifiedName>,
126) -> bool {
127 params.iter().any(|param| match param {
128 FnArg::Typed(PatType { ty, .. }) => match ty.as_ref() {
129 Type::Reference(TypeReference { elem, .. }) => match elem.as_ref() {
130 Type::Path(typ) => {
131 let qn = QualifiedName::from_type_path(typ);
132 non_pod_types.contains(&qn)
133 }
134 _ => false,
135 },
136 _ => false,
137 },
138 _ => false,
139 })
140}
141
142fn return_type_is_pod_or_known_type_reference(
143 ret_type: &ReturnType,
144 non_pod_types: &HashSet<QualifiedName>,
145) -> bool {
146 match ret_type {
147 ReturnType::Type(_, boxed_type) => match boxed_type.as_ref() {
148 Type::Reference(rtr) => match rtr.elem.as_ref() {
149 Type::Path(typ) => {
150 let qn = QualifiedName::from_type_path(typ);
151 !non_pod_types.contains(&qn)
152 }
153 _ => false,
154 },
155 _ => false,
156 },
157 _ => false,
158 }
159}
160
161fn return_type_is_impl(ret_type: &ReturnType) -> bool {
162 matches!(ret_type, ReturnType::Type(_, boxed_type) if matches!(boxed_type.as_ref(), Type::ImplTrait(..)))
163}
164
165#[derive(Debug)]
166enum AddLifetimeError {
167 WasNotPin,
168}
169
170fn add_lifetime_to_pinned_reference(
171 segments: &mut Punctuated<PathSegment, syn::token::Colon2>,
172) -> Result<(), AddLifetimeError> {
173 static EXPECTED_SEGMENTS: &[(&str, bool)] = &[
174 ("std", false),
175 ("pin", false),
176 ("Pin", true), // true = act on the arguments of this segment
177 ];
178
179 for (seg, (expected_name, act)) in segments.iter_mut().zip(EXPECTED_SEGMENTS.iter()) {
180 if seg.ident != expected_name {
181 return Err(AddLifetimeError::WasNotPin);
182 }
183 if *act {
184 match &mut seg.arguments {
185 syn::PathArguments::AngleBracketed(aba) => match aba.args.iter_mut().next() {
186 Some(GenericArgument::Type(Type::Reference(tyr))) => {
187 add_lifetime_to_reference(tyr);
188 }
189 _ => panic!("Expected generic args with a reference"),
190 },
191 _ => panic!("Expected angle bracketed args"),
192 }
193 }
194 }
195 Ok(())
196}
197
198fn add_lifetime_to_reference(tyr: &mut syn::TypeReference) {
199 tyr.lifetime = Some(parse_quote! { 'a })
200}
201
202fn add_lifetime_to_impl_trait(tyit: &mut syn::TypeImplTrait) {
203 tyit.bounds
204 .push(syn::TypeParamBound::Lifetime(parse_quote! { 'a }))
205}