Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix handling of Self keyword in type definition
- Loading branch information
Showing
8 changed files
with
316 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
use super::respan::respan; | ||
use proc_macro2::{Group, Spacing, Span, TokenStream, TokenTree}; | ||
use quote::{quote, quote_spanned}; | ||
use std::{iter::FromIterator, mem}; | ||
use syn::{ | ||
parse_quote, | ||
punctuated::Punctuated, | ||
token, | ||
visit_mut::{self, VisitMut}, | ||
DeriveInput, ExprPath, Macro, Path, PathArguments, QSelf, Type, TypePath, | ||
}; | ||
|
||
pub fn replace_receiver(input: &mut DeriveInput) { | ||
let ident = &input.ident; | ||
let ty_generics = input.generics.split_for_impl().1; | ||
let self_ty = parse_quote!(#ident #ty_generics); | ||
let mut visitor = ReplaceReceiver(&self_ty); | ||
visitor.visit_generics_mut(&mut input.generics); | ||
visitor.visit_data_mut(&mut input.data); | ||
} | ||
|
||
struct ReplaceReceiver<'a>(&'a TypePath); | ||
|
||
impl ReplaceReceiver<'_> { | ||
fn self_ty(&self, span: Span) -> TypePath { | ||
respan(self.0, span) | ||
} | ||
|
||
fn self_to_qself(&self, qself: &mut Option<QSelf>, path: &mut Path) { | ||
if path.leading_colon.is_some() { | ||
return; | ||
} | ||
|
||
let first = &path.segments[0]; | ||
if first.ident != "Self" || !first.arguments.is_empty() { | ||
return; | ||
} | ||
|
||
if path.segments.len() == 1 { | ||
self.self_to_expr_path(path); | ||
return; | ||
} | ||
|
||
let span = first.ident.span(); | ||
*qself = Some(QSelf { | ||
lt_token: token::Lt(span), | ||
ty: Box::new(self.self_ty(span).into()), | ||
position: 0, | ||
as_token: None, | ||
gt_token: token::Gt(span), | ||
}); | ||
|
||
path.leading_colon = Some(**path.segments.pairs().next().unwrap().punct().unwrap()); | ||
|
||
let segments = mem::replace(&mut path.segments, Punctuated::new()); | ||
path.segments = segments.into_pairs().skip(1).collect(); | ||
} | ||
|
||
fn self_to_expr_path(&self, path: &mut Path) { | ||
if path.leading_colon.is_some() { | ||
return; | ||
} | ||
|
||
let first = &path.segments[0]; | ||
if first.ident != "Self" || !first.arguments.is_empty() { | ||
return; | ||
} | ||
|
||
let self_ty = self.self_ty(first.ident.span()); | ||
let variant = mem::replace(path, self_ty.path); | ||
for segment in &mut path.segments { | ||
if let PathArguments::AngleBracketed(bracketed) = &mut segment.arguments { | ||
if bracketed.colon2_token.is_none() && !bracketed.args.is_empty() { | ||
bracketed.colon2_token = Some(Default::default()); | ||
} | ||
} | ||
} | ||
if variant.segments.len() > 1 { | ||
path.segments.push_punct(Default::default()); | ||
path.segments.extend(variant.segments.into_pairs().skip(1)); | ||
} | ||
} | ||
|
||
fn visit_token_stream(&self, tokens: &mut TokenStream) -> bool { | ||
let mut out = Vec::new(); | ||
let mut modified = false; | ||
let mut iter = tokens.clone().into_iter().peekable(); | ||
while let Some(tt) = iter.next() { | ||
match tt { | ||
TokenTree::Ident(ident) => { | ||
if ident == "Self" { | ||
modified = true; | ||
let self_ty = self.self_ty(ident.span()); | ||
match iter.peek() { | ||
Some(TokenTree::Punct(p)) | ||
if p.as_char() == ':' && p.spacing() == Spacing::Joint => | ||
{ | ||
let next = iter.next().unwrap(); | ||
match iter.peek() { | ||
Some(TokenTree::Punct(p)) if p.as_char() == ':' => { | ||
let span = ident.span(); | ||
out.extend(quote_spanned!(span=> <#self_ty>)); | ||
} | ||
_ => out.extend(quote!(#self_ty)), | ||
} | ||
out.push(next); | ||
} | ||
_ => out.extend(quote!(#self_ty)), | ||
} | ||
} else { | ||
out.push(TokenTree::Ident(ident)); | ||
} | ||
} | ||
TokenTree::Group(group) => { | ||
let mut content = group.stream(); | ||
modified |= self.visit_token_stream(&mut content); | ||
let mut new = Group::new(group.delimiter(), content); | ||
new.set_span(group.span()); | ||
out.push(TokenTree::Group(new)); | ||
} | ||
other => out.push(other), | ||
} | ||
} | ||
if modified { | ||
*tokens = TokenStream::from_iter(out); | ||
} | ||
modified | ||
} | ||
} | ||
|
||
impl VisitMut for ReplaceReceiver<'_> { | ||
// `Self` -> `Receiver` | ||
fn visit_type_mut(&mut self, ty: &mut Type) { | ||
if let Type::Path(node) = ty { | ||
if node.qself.is_none() && node.path.is_ident("Self") { | ||
*ty = self.self_ty(node.path.segments[0].ident.span()).into(); | ||
} else { | ||
self.visit_type_path_mut(node); | ||
} | ||
} else { | ||
visit_mut::visit_type_mut(self, ty); | ||
} | ||
} | ||
|
||
// `Self::Assoc` -> `<Receiver>::Assoc` | ||
fn visit_type_path_mut(&mut self, ty: &mut TypePath) { | ||
if ty.qself.is_none() { | ||
self.self_to_qself(&mut ty.qself, &mut ty.path); | ||
} | ||
visit_mut::visit_type_path_mut(self, ty); | ||
} | ||
|
||
// `Self::method` -> `<Receiver>::method` | ||
fn visit_expr_path_mut(&mut self, expr: &mut ExprPath) { | ||
if expr.qself.is_none() { | ||
self.self_to_qself(&mut expr.qself, &mut expr.path); | ||
} | ||
visit_mut::visit_expr_path_mut(self, expr); | ||
} | ||
|
||
fn visit_macro_mut(&mut self, mac: &mut Macro) { | ||
// We can't tell in general whether `self` inside a macro invocation | ||
// refers to the self in the argument list or a different self | ||
// introduced within the macro. Heuristic: if the macro input contains | ||
// `fn`, then `self` is more likely to refer to something other than the | ||
// outer function's self argument. | ||
if !contains_fn(mac.tokens.clone()) { | ||
self.visit_token_stream(&mut mac.tokens); | ||
} | ||
} | ||
} | ||
|
||
fn contains_fn(tokens: TokenStream) -> bool { | ||
tokens.into_iter().any(|tt| match tt { | ||
TokenTree::Ident(ident) => ident == "fn", | ||
TokenTree::Group(group) => contains_fn(group.stream()), | ||
_ => false, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use proc_macro2::{Span, TokenStream}; | ||
use quote::ToTokens; | ||
use syn::parse::Parse; | ||
|
||
pub(crate) fn respan<T>(node: &T, span: Span) -> T | ||
where | ||
T: ToTokens + Parse, | ||
{ | ||
let tokens = node.to_token_stream(); | ||
let respanned = respan_tokens(tokens, span); | ||
syn::parse2(respanned).unwrap() | ||
} | ||
|
||
fn respan_tokens(tokens: TokenStream, span: Span) -> TokenStream { | ||
tokens | ||
.into_iter() | ||
.map(|mut token| { | ||
token.set_span(span); | ||
token | ||
}) | ||
.collect() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[test] | ||
fn test_self() { | ||
macro_rules! mac { | ||
($($tt:tt)*) => { | ||
$($tt)* | ||
}; | ||
} | ||
|
||
pub trait Trait { | ||
type Assoc; | ||
} | ||
|
||
#[derive(Deserialize, Serialize)] | ||
pub struct Generics<T: Trait<Assoc = Self>> | ||
where | ||
Self: Trait<Assoc = Self>, | ||
<Self as Trait>::Assoc: Sized, | ||
mac!(Self): Trait<Assoc = mac!(Self)>, | ||
{ | ||
_f: T, | ||
} | ||
|
||
impl<T: Trait<Assoc = Self>> Trait for Generics<T> { | ||
type Assoc = Self; | ||
} | ||
|
||
#[derive(Deserialize, Serialize)] | ||
pub struct Struct { | ||
_f1: Box<Self>, | ||
_f2: Box<<Self as Trait>::Assoc>, | ||
_f3: Box<mac!(Self)>, | ||
_f4: [(); Self::ASSOC], | ||
_f5: [(); Self::assoc()], | ||
_f6: [(); mac!(Self::assoc())], | ||
} | ||
|
||
impl Struct { | ||
const ASSOC: usize = 1; | ||
const fn assoc() -> usize { | ||
0 | ||
} | ||
} | ||
|
||
impl Trait for Struct { | ||
type Assoc = Self; | ||
} | ||
|
||
#[derive(Deserialize, Serialize)] | ||
struct Tuple( | ||
Box<Self>, | ||
Box<<Self as Trait>::Assoc>, | ||
Box<mac!(Self)>, | ||
[(); Self::ASSOC], | ||
[(); Self::assoc()], | ||
[(); mac!(Self::assoc())], | ||
); | ||
|
||
impl Tuple { | ||
const ASSOC: usize = 1; | ||
const fn assoc() -> usize { | ||
0 | ||
} | ||
} | ||
|
||
impl Trait for Tuple { | ||
type Assoc = Self; | ||
} | ||
|
||
#[derive(Deserialize, Serialize)] | ||
enum Enum { | ||
Struct { | ||
_f1: Box<Self>, | ||
_f2: Box<<Self as Trait>::Assoc>, | ||
_f3: Box<mac!(Self)>, | ||
_f4: [(); Self::ASSOC], | ||
_f5: [(); Self::assoc()], | ||
_f6: [(); mac!(Self::assoc())], | ||
}, | ||
Tuple( | ||
Box<Self>, | ||
Box<<Self as Trait>::Assoc>, | ||
Box<mac!(Self)>, | ||
[(); Self::ASSOC], | ||
[(); Self::assoc()], | ||
[(); mac!(Self::assoc())], | ||
), | ||
} | ||
|
||
impl Enum { | ||
const ASSOC: usize = 1; | ||
const fn assoc() -> usize { | ||
0 | ||
} | ||
} | ||
|
||
impl Trait for Enum { | ||
type Assoc = Self; | ||
} | ||
} |