|
| 1 | +#![allow(unused_imports, unused_variables)] |
| 2 | + |
| 3 | +use rustc_ast::token; |
| 4 | +use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; |
| 5 | +use rustc_errors::ErrorGuaranteed; |
| 6 | +use rustc_expand::base::{AttrProcMacro, ExtCtxt}; |
| 7 | +use rustc_span::Span; |
| 8 | +use rustc_span::symbol::{Ident, Symbol, kw, sym}; |
| 9 | + |
| 10 | +pub(crate) struct ExpandRequires; |
| 11 | + |
| 12 | +pub(crate) struct ExpandEnsures; |
| 13 | + |
| 14 | +impl AttrProcMacro for ExpandRequires { |
| 15 | + fn expand<'cx>( |
| 16 | + &self, |
| 17 | + ecx: &'cx mut ExtCtxt<'_>, |
| 18 | + span: Span, |
| 19 | + annotation: TokenStream, |
| 20 | + annotated: TokenStream, |
| 21 | + ) -> Result<TokenStream, ErrorGuaranteed> { |
| 22 | + expand_requires_tts(ecx, span, annotation, annotated) |
| 23 | + } |
| 24 | +} |
| 25 | + |
| 26 | +impl AttrProcMacro for ExpandEnsures { |
| 27 | + fn expand<'cx>( |
| 28 | + &self, |
| 29 | + ecx: &'cx mut ExtCtxt<'_>, |
| 30 | + span: Span, |
| 31 | + annotation: TokenStream, |
| 32 | + annotated: TokenStream, |
| 33 | + ) -> Result<TokenStream, ErrorGuaranteed> { |
| 34 | + expand_ensures_tts(ecx, span, annotation, annotated) |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +fn expand_injecting_circa_where_clause( |
| 39 | + _ecx: &mut ExtCtxt<'_>, |
| 40 | + attr_span: Span, |
| 41 | + annotated: TokenStream, |
| 42 | + inject: impl FnOnce(&mut Vec<TokenTree>) -> Result<(), ErrorGuaranteed>, |
| 43 | +) -> Result<TokenStream, ErrorGuaranteed> { |
| 44 | + let mut new_tts = Vec::with_capacity(annotated.len()); |
| 45 | + let mut cursor = annotated.into_trees(); |
| 46 | + |
| 47 | + // Find the `fn name<G,...>(x:X,...)` and inject the AST contract forms right after |
| 48 | + // the formal parameters (and return type if any). |
| 49 | + while let Some(tt) = cursor.next_ref() { |
| 50 | + new_tts.push(tt.clone()); |
| 51 | + if let TokenTree::Token(tok, _) = tt |
| 52 | + && tok.is_ident_named(kw::Fn) |
| 53 | + { |
| 54 | + break; |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + // Found the `fn` keyword, now find the formal parameters. |
| 59 | + // |
| 60 | + // FIXME: can this fail if you have parentheticals in a generics list, like `fn foo<F: Fn(X) -> Y>` ? |
| 61 | + while let Some(tt) = cursor.next_ref() { |
| 62 | + new_tts.push(tt.clone()); |
| 63 | + |
| 64 | + if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt { |
| 65 | + break; |
| 66 | + } |
| 67 | + if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = tt { |
| 68 | + panic!("contract attribute applied to fn without parameter list."); |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + // There *might* be a return type declaration (and figuring out where that ends would require |
| 73 | + // parsing an arbitrary type expression, e.g. `-> Foo<args ...>` |
| 74 | + // |
| 75 | + // Instead of trying to figure that out, scan ahead and look for the first occurence of a |
| 76 | + // `where`, a `{ ... }`, or a `;`. |
| 77 | + // |
| 78 | + // FIXME: this might still fall into a trap for something like `-> Ctor<T, const { 0 }>`. I |
| 79 | + // *think* such cases must be under a Delimited (e.g. `[T; { N }]` or have the braced form |
| 80 | + // prefixed by e.g. `const`, so we should still be able to filter them out without having to |
| 81 | + // parse the type expression itself. But rather than try to fix things with hacks like that, |
| 82 | + // time might be better spent extending the attribute expander to suport tt-annotation atop |
| 83 | + // ast-annotated, which would be an elegant way to sidestep all of this. |
| 84 | + let mut opt_next_tt = cursor.next_ref(); |
| 85 | + while let Some(next_tt) = opt_next_tt { |
| 86 | + if let TokenTree::Token(tok, _) = next_tt |
| 87 | + && tok.is_ident_named(kw::Where) |
| 88 | + { |
| 89 | + break; |
| 90 | + } |
| 91 | + if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = next_tt { |
| 92 | + break; |
| 93 | + } |
| 94 | + if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = next_tt { |
| 95 | + break; |
| 96 | + } |
| 97 | + |
| 98 | + // for anything else, transcribe the tt and keep looking. |
| 99 | + new_tts.push(next_tt.clone()); |
| 100 | + opt_next_tt = cursor.next_ref(); |
| 101 | + continue; |
| 102 | + } |
| 103 | + |
| 104 | + // At this point, we've transcribed everything from the `fn` through the formal parameter list |
| 105 | + // and return type declaration, (if any), but `tt` itself has *not* been transcribed. |
| 106 | + // |
| 107 | + // Now inject the AST contract form. |
| 108 | + // |
| 109 | + // FIXME: this kind of manual token tree munging does not have significant precedent among |
| 110 | + // rustc builtin macros, probably because most builtin macros use direct AST manipulation to |
| 111 | + // accomplish similar goals. But since our attributes need to take arbitrary expressions, and |
| 112 | + // our attribute infrastructure does not yet support mixing a token-tree annotation with an AST |
| 113 | + // annotated, we end up doing token tree manipulation. |
| 114 | + inject(&mut new_tts)?; |
| 115 | + |
| 116 | + // Above we injected the internal AST requires/ensures contruct. Now copy over all the other |
| 117 | + // token trees. |
| 118 | + if let Some(tt) = opt_next_tt { |
| 119 | + new_tts.push(tt.clone()); |
| 120 | + } |
| 121 | + while let Some(tt) = cursor.next_ref() { |
| 122 | + new_tts.push(tt.clone()); |
| 123 | + } |
| 124 | + |
| 125 | + Ok(TokenStream::new(new_tts)) |
| 126 | +} |
| 127 | + |
| 128 | +fn expand_requires_tts( |
| 129 | + _ecx: &mut ExtCtxt<'_>, |
| 130 | + attr_span: Span, |
| 131 | + annotation: TokenStream, |
| 132 | + annotated: TokenStream, |
| 133 | +) -> Result<TokenStream, ErrorGuaranteed> { |
| 134 | + expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { |
| 135 | + new_tts.push(TokenTree::Token( |
| 136 | + token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)), |
| 137 | + Spacing::Joint, |
| 138 | + )); |
| 139 | + new_tts.push(TokenTree::Token( |
| 140 | + token::Token::new(token::TokenKind::OrOr, attr_span), |
| 141 | + Spacing::Alone, |
| 142 | + )); |
| 143 | + new_tts.push(TokenTree::Delimited( |
| 144 | + DelimSpan::from_single(attr_span), |
| 145 | + DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), |
| 146 | + token::Delimiter::Parenthesis, |
| 147 | + annotation, |
| 148 | + )); |
| 149 | + Ok(()) |
| 150 | + }) |
| 151 | +} |
| 152 | + |
| 153 | +fn expand_ensures_tts( |
| 154 | + _ecx: &mut ExtCtxt<'_>, |
| 155 | + attr_span: Span, |
| 156 | + annotation: TokenStream, |
| 157 | + annotated: TokenStream, |
| 158 | +) -> Result<TokenStream, ErrorGuaranteed> { |
| 159 | + expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| { |
| 160 | + new_tts.push(TokenTree::Token( |
| 161 | + token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, attr_span)), |
| 162 | + Spacing::Joint, |
| 163 | + )); |
| 164 | + new_tts.push(TokenTree::Delimited( |
| 165 | + DelimSpan::from_single(attr_span), |
| 166 | + DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), |
| 167 | + token::Delimiter::Parenthesis, |
| 168 | + annotation, |
| 169 | + )); |
| 170 | + Ok(()) |
| 171 | + }) |
| 172 | +} |
0 commit comments