| use rustc_ast::token; | |
| use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; | |
| use rustc_errors::ErrorGuaranteed; | |
| use rustc_expand::base::{AttrProcMacro, ExtCtxt}; | |
| use rustc_span::Span; | |
| use rustc_span::symbol::{Ident, Symbol, kw}; | |
| pub(crate) struct ExpandRequires; | |
| pub(crate) struct ExpandEnsures; | |
| impl AttrProcMacro for ExpandRequires { | |
| fn expand<'cx>( | |
| &self, | |
| ecx: &'cx mut ExtCtxt<'_>, | |
| span: Span, | |
| annotation: TokenStream, | |
| annotated: TokenStream, | |
| ) -> Result<TokenStream, ErrorGuaranteed> { | |
| expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractRequires) | |
| } | |
| } | |
| impl AttrProcMacro for ExpandEnsures { | |
| fn expand<'cx>( | |
| &self, | |
| ecx: &'cx mut ExtCtxt<'_>, | |
| span: Span, | |
| annotation: TokenStream, | |
| annotated: TokenStream, | |
| ) -> Result<TokenStream, ErrorGuaranteed> { | |
| expand_contract_clause_tts(ecx, span, annotation, annotated, kw::ContractEnsures) | |
| } | |
| } | |
| /// Expand the function signature to include the contract clause. | |
| /// | |
| /// The contracts clause will be injected before the function body and the optional where clause. | |
| /// For that, we search for the body / where token, and invoke the `inject` callback to generate the | |
| /// contract clause in the right place. | |
| /// | |
| // FIXME: this kind of manual token tree munging does not have significant precedent among | |
| // rustc builtin macros, probably because most builtin macros use direct AST manipulation to | |
| // accomplish similar goals. But since our attributes need to take arbitrary expressions, and | |
| // our attribute infrastructure does not yet support mixing a token-tree annotation with an AST | |
| // annotated, we end up doing token tree manipulation. | |
| fn expand_contract_clause( | |
| ecx: &mut ExtCtxt<'_>, | |
| attr_span: Span, | |
| annotated: TokenStream, | |
| inject: impl FnOnce(&mut Vec<TokenTree>) -> Result<(), ErrorGuaranteed>, | |
| ) -> Result<TokenStream, ErrorGuaranteed> { | |
| let mut new_tts = vec![]; | |
| let mut cursor = annotated.iter(); | |
| let is_kw = |tt: &TokenTree, sym: Symbol| { | |
| if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false } | |
| }; | |
| // Find the `fn` keyword to check if this is a function. | |
| if cursor | |
| .find(|tt| { | |
| new_tts.push((*tt).clone()); | |
| is_kw(tt, kw::Fn) | |
| }) | |
| .is_none() | |
| { | |
| return Err(ecx | |
| .sess | |
| .dcx() | |
| .span_err(attr_span, "contract annotations can only be used on functions")); | |
| } | |
| // Contracts are not yet supported on async/gen functions | |
| if new_tts.iter().any(|tt| is_kw(tt, kw::Async) || is_kw(tt, kw::Gen)) { | |
| return Err(ecx.sess.dcx().span_err( | |
| attr_span, | |
| "contract annotations are not yet supported on async or gen functions", | |
| )); | |
| } | |
| // Found the `fn` keyword, now find either the `where` token or the function body. | |
| let next_tt = loop { | |
| let Some(tt) = cursor.next() else { | |
| return Err(ecx.sess.dcx().span_err( | |
| attr_span, | |
| "contract annotations is only supported in functions with bodies", | |
| )); | |
| }; | |
| // If `tt` is the last element. Check if it is the function body. | |
| if cursor.peek().is_none() { | |
| if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt { | |
| break tt; | |
| } else { | |
| return Err(ecx.sess.dcx().span_err( | |
| attr_span, | |
| "contract annotations is only supported in functions with bodies", | |
| )); | |
| } | |
| } | |
| if is_kw(tt, kw::Where) { | |
| break tt; | |
| } | |
| new_tts.push(tt.clone()); | |
| }; | |
| // At this point, we've transcribed everything from the `fn` through the formal parameter list | |
| // and return type declaration, (if any), but `tt` itself has *not* been transcribed. | |
| // | |
| // Now inject the AST contract form. | |
| // | |
| inject(&mut new_tts)?; | |
| // Above we injected the internal AST requires/ensures construct. Now copy over all the other | |
| // token trees. | |
| new_tts.push(next_tt.clone()); | |
| while let Some(tt) = cursor.next() { | |
| new_tts.push(tt.clone()); | |
| if cursor.peek().is_none() | |
| && !matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _)) | |
| { | |
| return Err(ecx.sess.dcx().span_err( | |
| attr_span, | |
| "contract annotations is only supported in functions with bodies", | |
| )); | |
| } | |
| } | |
| Ok(TokenStream::new(new_tts)) | |
| } | |
| fn expand_contract_clause_tts( | |
| ecx: &mut ExtCtxt<'_>, | |
| attr_span: Span, | |
| annotation: TokenStream, | |
| annotated: TokenStream, | |
| clause_keyword: rustc_span::Symbol, | |
| ) -> Result<TokenStream, ErrorGuaranteed> { | |
| let feature_span = ecx.with_def_site_ctxt(attr_span); | |
| expand_contract_clause(ecx, attr_span, annotated, |new_tts| { | |
| new_tts.push(TokenTree::Token( | |
| token::Token::from_ast_ident(Ident::new(clause_keyword, feature_span)), | |
| Spacing::Joint, | |
| )); | |
| new_tts.push(TokenTree::Delimited( | |
| DelimSpan::from_single(attr_span), | |
| DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden), | |
| token::Delimiter::Brace, | |
| annotation, | |
| )); | |
| Ok(()) | |
| }) | |
| } |