Skip to content

Commit daa26b2

Browse files
authored
Merge pull request #28 from kas-gui/work
Bound for-deref types with Deref/DerefMut for better error messages
2 parents 714be0d + 5c32cca commit daa26b2

8 files changed

Lines changed: 324 additions & 117 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
33
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5+
## [0.6.1], `impl-tools-lib` [0.7.0] — 2022-12-01
6+
7+
- Better diagnostics for trait-redefinition: require `Deref` bound (#28)
8+
- Document `Deref` with custom `Target` type
9+
10+
`impl-tools-lib` has breaking changes and therefore a higher version number:
11+
12+
- Replace free function `impl_generics` with method `Generics::impl_generics`
13+
- Add method `Generics::ty_generics`
14+
15+
Note: next breaking release for `impl-tools` should bump version to match `-lib`.
16+
517
## [0.6.0] — 2022-11-17
618

719
- Add `ImplTrait::support_path_args`, `ImplArgs::path_args` (#26)

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "impl-tools"
3-
version = "0.6.0"
3+
version = "0.6.1"
44
authors = ["Diggory Hardy <git@dhardy.name>"]
55
edition = "2021"
66
license = "MIT/Apache-2.0"
@@ -21,7 +21,7 @@ proc-macro-error = "1.0"
2121
version = "1.0.14"
2222

2323
[dependencies.impl-tools-lib]
24-
version = "0.6.0"
24+
version = "0.7.0"
2525
path = "lib"
2626

2727
[dev-dependencies]

lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "impl-tools-lib"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
authors = ["Diggory Hardy <git@dhardy.name>"]
55
edition = "2021"
66
license = "MIT/Apache-2.0"

lib/src/autoimpl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ pub struct ImplArgs {
366366
/// Path arguments to trait
367367
///
368368
/// Example: if the target is `Deref<Target = T>`, this is `<Target = T>`.
369-
/// This is always empty unless [`ImplTrait::support_path_args`] returns true.
369+
/// This is always empty unless [`ImplTrait::support_path_arguments`] returns true.
370370
pub path_arguments: PathArguments,
371371
/// Fields ignored in attribute
372372
pub ignores: Vec<Member>,

lib/src/for_deref.rs

Lines changed: 140 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
//! Implementation of the `#[autoimpl]` attribute
77
8-
use crate::generics::{impl_generics, GenericParam, Generics, TypeParamBound, WherePredicate};
8+
use crate::generics::{GenericParam, Generics, TypeParamBound, WherePredicate};
99
use proc_macro2::{Span, TokenStream};
1010
use proc_macro_error::{emit_call_site_error, emit_error};
1111
use quote::{quote, ToTokens, TokenStreamExt};
1212
use std::{iter, slice};
1313
use syn::punctuated::Punctuated;
1414
use syn::spanned::Spanned;
1515
use syn::token::{Colon2, Comma, Eq};
16-
use syn::{Attribute, FnArg, Ident, Item, Token, TraitItem, Type, TypePath};
16+
use syn::{parse_quote, Attribute, FnArg, Ident, Item, Token, TraitItem, Type, TypePath};
1717

1818
/// Autoimpl for types supporting `Deref`
1919
pub struct ForDeref {
@@ -143,9 +143,9 @@ impl ForDeref {
143143
/// Expand over the given `item`
144144
///
145145
/// This attribute does not modify the item.
146-
/// The caller should append the result to `item` impl_items.
146+
/// The caller should append the result to `item` tokens.
147147
pub fn expand(self, item: TokenStream) -> TokenStream {
148-
let item = match syn::parse2::<Item>(item) {
148+
let trait_def = match syn::parse2::<Item>(item) {
149149
Ok(Item::Trait(item)) => item,
150150
Ok(item) => {
151151
emit_error!(item, "expected trait");
@@ -157,100 +157,160 @@ impl ForDeref {
157157
}
158158
};
159159

160-
let trait_ident = &item.ident;
161-
let (_, ty_generics, _) = item.generics.split_for_impl();
162-
let trait_ty = quote! { #trait_ident #ty_generics };
163-
let (impl_generics, where_clause) = impl_generics(self.generics, &item.generics, &trait_ty);
160+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
161+
enum Bound {
162+
None,
163+
Deref(bool), // true if DerefMut
164+
ErrorEmitted,
165+
}
166+
let mut bound = Bound::None;
167+
168+
let trait_ident = &trait_def.ident;
169+
let (_, trait_generics, _) = trait_def.generics.split_for_impl();
170+
let trait_ty = quote! { #trait_ident #trait_generics };
171+
let ty_generics = self.generics.ty_generics(&trait_def.generics);
172+
let (impl_generics, where_clause) =
173+
self.generics.impl_generics(&trait_def.generics, &trait_ty);
174+
175+
let definitive_ty = self.definitive;
176+
let definitive = quote! { < #definitive_ty as #trait_ty > };
177+
178+
// Tokenize, like ToTokens impls for syn::TraitItem*, but for definition
179+
let mut impl_items = TokenStream::new();
180+
let tokens = &mut impl_items;
181+
for item in &trait_def.items {
182+
match item {
183+
TraitItem::Const(item) => {
184+
item.const_token.to_tokens(tokens);
185+
item.ident.to_tokens(tokens);
186+
item.colon_token.to_tokens(tokens);
187+
item.ty.to_tokens(tokens);
188+
189+
Eq::default().to_tokens(tokens);
190+
definitive.to_tokens(tokens);
191+
Colon2::default().to_tokens(tokens);
192+
item.ident.to_tokens(tokens);
193+
194+
item.semi_token.to_tokens(tokens);
195+
}
196+
TraitItem::Method(item) => {
197+
if has_bound_on_self(&item.sig.generics) {
198+
// If the method has a bound on Self, we cannot use a dereferencing
199+
// implementation since the definitive type is not guaranteed to match
200+
// the bound (we also cannot add a bound).
164201

165-
let definitive = self.definitive;
166-
let definitive = quote! { < #definitive as #trait_ty > };
202+
if item.default.is_none() {
203+
emit_call_site_error!(
204+
"cannot autoimpl trait with Deref";
205+
note = item.span() => "method has a bound on Self and no default implementation";
206+
);
207+
}
167208

168-
let mut toks = TokenStream::new();
169-
for target in self.targets {
170-
// Tokenize, like ToTokens impls for syn::TraitItem*, but for definition
171-
let mut impl_items = TokenStream::new();
172-
let tokens = &mut impl_items;
173-
for item in &item.items {
174-
match item {
175-
TraitItem::Const(item) => {
176-
item.const_token.to_tokens(tokens);
177-
item.ident.to_tokens(tokens);
178-
item.colon_token.to_tokens(tokens);
179-
item.ty.to_tokens(tokens);
180-
181-
Eq::default().to_tokens(tokens);
182-
definitive.to_tokens(tokens);
183-
Colon2::default().to_tokens(tokens);
184-
item.ident.to_tokens(tokens);
185-
186-
item.semi_token.to_tokens(tokens);
209+
continue;
187210
}
188-
TraitItem::Method(item) => {
189-
if has_bound_on_self(&item.sig.generics) {
190-
// If the method has a bound on Self, we cannot use a dereferencing
191-
// implementation since the definitive type is not guaranteed to match
192-
// the bound (we also cannot add a bound).
193211

194-
if item.default.is_none() {
212+
item.sig.to_tokens(tokens);
213+
214+
bound = bound.max(match item.sig.inputs.first() {
215+
Some(FnArg::Receiver(rec)) => {
216+
if rec.reference.is_some() {
217+
Bound::Deref(rec.mutability.is_some())
218+
} else {
195219
emit_call_site_error!(
196-
"unable to write automatic trait implementations";
197-
note = item.span() => "this method has a bound on Self and no default implementation";
220+
"cannot autoimpl trait with Deref";
221+
note = rec.span() => "deref cannot yield `self` by value";
198222
);
223+
Bound::ErrorEmitted
199224
}
200-
201-
continue;
202225
}
226+
Some(FnArg::Typed(ref pat)) => match &*pat.ty {
227+
Type::Reference(rf) if rf.elem == parse_quote! { Self } => {
228+
Bound::Deref(rf.mutability.is_some())
229+
}
230+
_ => Bound::None,
231+
},
232+
_ => Bound::None,
233+
});
234+
235+
let ident = &item.sig.ident;
236+
let params = item.sig.inputs.iter().map(|arg| match arg {
237+
FnArg::Receiver(arg) => &arg.self_token as &dyn ToTokens,
238+
FnArg::Typed(arg) => &arg.pat,
239+
});
240+
tokens.append_all(quote! { {
241+
#definitive :: #ident ( #(#params),* )
242+
} });
243+
}
244+
TraitItem::Type(item) => {
245+
if has_bound_on_self(&item.generics) {
246+
emit_call_site_error!(
247+
"cannot autoimpl trait with Deref";
248+
note = item.span() => "type has a bound on Self";
249+
);
250+
}
203251

204-
item.sig.to_tokens(tokens);
252+
item.type_token.to_tokens(tokens);
253+
item.ident.to_tokens(tokens);
205254

206-
let ident = &item.sig.ident;
207-
let params = item.sig.inputs.iter().map(|arg| match arg {
208-
FnArg::Receiver(arg) => &arg.self_token as &dyn ToTokens,
209-
FnArg::Typed(arg) => &arg.pat,
210-
});
211-
tokens.append_all(quote! { {
212-
#definitive :: #ident ( #(#params),* )
213-
} });
214-
}
215-
TraitItem::Type(item) => {
216-
if has_bound_on_self(&item.generics) {
217-
emit_call_site_error!(
218-
"unable to write automatic trait implementations";
219-
note = item.span() => "this type has a bound on Self";
220-
);
221-
}
255+
let (_, ty_generics, where_clause) = item.generics.split_for_impl();
256+
ty_generics.to_tokens(tokens);
222257

223-
item.type_token.to_tokens(tokens);
224-
item.ident.to_tokens(tokens);
258+
Eq::default().to_tokens(tokens);
259+
definitive.to_tokens(tokens);
260+
Colon2::default().to_tokens(tokens);
261+
item.ident.to_tokens(tokens);
262+
ty_generics.to_tokens(tokens);
225263

226-
let (_, ty_generics, where_clause) = item.generics.split_for_impl();
227-
ty_generics.to_tokens(tokens);
264+
where_clause.to_tokens(tokens);
265+
item.semi_token.to_tokens(tokens);
266+
}
267+
TraitItem::Macro(item) => {
268+
emit_error!(item, "unsupported: macro item in trait");
269+
}
270+
TraitItem::Verbatim(item) => {
271+
emit_error!(item, "unsupported: verbatim item in trait");
272+
}
228273

229-
Eq::default().to_tokens(tokens);
230-
definitive.to_tokens(tokens);
231-
Colon2::default().to_tokens(tokens);
232-
item.ident.to_tokens(tokens);
233-
ty_generics.to_tokens(tokens);
274+
/* Testing of exhaustive matching is disabled: syn 1.0.90 breaks it.
275+
#[cfg(test)]
276+
TraitItem::__TestExhaustive(_) => unimplemented!(),
277+
#[cfg(not(test))]
278+
*/
279+
_ => (),
280+
}
281+
}
234282

235-
where_clause.to_tokens(tokens);
236-
item.semi_token.to_tokens(tokens);
237-
}
238-
TraitItem::Macro(item) => {
239-
emit_error!(item, "unsupported: macro item in trait");
240-
}
241-
TraitItem::Verbatim(item) => {
242-
emit_error!(item, "unsupported: verbatim item in trait");
283+
let mut toks = TokenStream::new();
284+
match bound {
285+
Bound::None => (),
286+
Bound::Deref(is_mut) => {
287+
// Emit a bound to improve error messages (see issue 27)
288+
let bound = match is_mut {
289+
false => quote! { ::core::ops::Deref },
290+
true => quote! { ::core::ops::DerefMut },
291+
};
292+
293+
let target_impls = self.targets.iter().map(|target| {
294+
quote! {
295+
impl #impl_generics TargetMustImplDeref #ty_generics for #target
296+
#where_clause {}
243297
}
298+
});
244299

245-
/* Testing of exhaustive matching is disabled: syn 1.0.90 breaks it.
246-
#[cfg(test)]
247-
TraitItem::__TestExhaustive(_) => unimplemented!(),
248-
#[cfg(not(test))]
249-
*/
250-
_ => (),
251-
}
300+
toks.append_all(quote! {
301+
#[automatically_derived]
302+
const _: () = {
303+
trait TargetMustImplDeref #impl_generics: #bound<Target = #definitive_ty>
304+
#where_clause {}
305+
306+
#(#target_impls)*
307+
};
308+
});
252309
}
310+
Bound::ErrorEmitted => return toks,
311+
}
253312

313+
for target in self.targets {
254314
toks.append_all(quote! {
255315
#[automatically_derived]
256316
impl #impl_generics #trait_ty for #target #where_clause {

0 commit comments

Comments
 (0)