@@ -632,6 +632,111 @@ impl<'a, 'b> Context<'a, 'b> {
632632 }
633633}
634634
635+ /// Expands `ensure_not_fmt_string_literal!(<where-literal>, <expr>)`
636+ /// into `<expr>`, but ensures that if `<expr>` is a string-literal,
637+ /// then it is not a potential format string literal.
638+ pub fn ensure_not_fmt_string_literal < ' cx > ( cx : & ' cx mut ExtCtxt ,
639+ sp : Span ,
640+ tts : & [ ast:: TokenTree ] )
641+ -> Box < base:: MacResult +' cx > {
642+ use fold:: Folder ;
643+ let takes_two_args = |cx : & ExtCtxt , rest| {
644+ cx. span_err ( sp, & format ! ( "`ensure_not_fmt_string_literal!` \
645+ takes 2 arguments, {}", rest) ) ;
646+ DummyResult :: expr ( sp)
647+ } ;
648+ let mut p = cx. new_parser_from_tts ( tts) ;
649+ if p. token == token:: Eof { return takes_two_args ( cx, "given 0" ) ; }
650+ let arg1 = cx. expander ( ) . fold_expr ( p. parse_expr ( ) ) ;
651+ if p. token == token:: Eof { return takes_two_args ( cx, "given 1" ) ; }
652+ if !panictry ! ( p. eat( & token:: Comma ) ) { return takes_two_args ( cx, "comma-separated" ) ; }
653+ if p. token == token:: Eof { return takes_two_args ( cx, "given 1" ) ; }
654+ let arg2 = cx. expander ( ) . fold_expr ( p. parse_expr ( ) ) ;
655+ if p. token != token:: Eof {
656+ takes_two_args ( cx, "given too many" ) ;
657+ // (but do not return; handle two provided, nonetheless)
658+ }
659+
660+ // First argument is name of where this was invoked (for error messages).
661+ let lit_str_with_extended_lifetime;
662+ let name: & str = match expr_string_lit ( cx, arg1) {
663+ Ok ( ( _, lit_str, _) ) => {
664+ lit_str_with_extended_lifetime = lit_str;
665+ & lit_str_with_extended_lifetime
666+ }
667+ Err ( expr) => {
668+ let msg = "first argument to `ensure_not_fmt_string_literal!` \
669+ must be string literal";
670+ cx. span_err ( expr. span , msg) ;
671+ // Compile proceeds using "ensure_not_fmt_string_literal"
672+ // as the name.
673+ "ensure_not_fmt_string_literal!"
674+ }
675+ } ;
676+
677+ // Second argument is the expression we are checking.
678+ let warning = |cx : & ExtCtxt , c : char | {
679+ cx. span_warn ( sp, & format ! ( "{} literal argument contains `{}`" , name, c) ) ;
680+ cx. span_note ( sp, "Is it meant to be a `format!` string?" ) ;
681+ cx. span_help ( sp, "You can wrap the argument in parentheses \
682+ to sidestep this warning") ;
683+ } ;
684+
685+ let expr = match expr_string_lit ( cx, arg2) {
686+ Err ( expr) => {
687+ // input was not a string literal; just ignore it.
688+ expr
689+ }
690+
691+ Ok ( ( expr, lit_str, _style) ) => {
692+ for c in lit_str. chars ( ) {
693+ if c == '{' || c == '}' {
694+ warning ( cx, c) ;
695+ break ;
696+ }
697+ }
698+ // we still return the expr itself, to allow catching of
699+ // further errors in the input.
700+ expr
701+ }
702+ } ;
703+
704+ let unstable_marker = cx. expr_path ( cx. path_global ( sp, vec ! [
705+ cx. ident_of_std( "core" ) ,
706+ cx. ident_of( "fmt" ) ,
707+ cx. ident_of( "ENSURE_NOT_FMT_STRING_LITERAL_IS_UNSTABLE" ) ] ) ) ;
708+
709+ // Total hack: We do not (yet) have hygienic-marking of stabilty.
710+ // Thus an unstable macro (like `ensure_not_fmt_string!`) can leak
711+ // through another macro (like `panic!`), where the latter is just
712+ // using the former as an implementation detail.
713+ //
714+ // The `#[allow_internal_unstable]` system does not suffice to
715+ // address this; it explicitly (as described on Issue #22899)
716+ // disallows the use of unstable functionality via a helper macro
717+ // like `ensure_not_fmt_string!`, by design.
718+ //
719+ // So, the hack: the `ensure_not_fmt_string!` macro has just one
720+ // stable client: `panic!`. So we give `panic!` a backdoor: we
721+ // allow its name literal string to give it stable access. Any
722+ // other argument that is passed in will cause us to emit the
723+ // unstable-marker, which will then be checked against the enabled
724+ // feature-set.
725+ //
726+ // This, combined with the awkward actual name of the unstable
727+ // macro (hint: the real name is far more awkward than the one
728+ // given in this comment) should suffice to ensure that people do
729+ // not accidentally commit to using it.
730+ let marker = if name == "unary `panic!`" {
731+ cx. expr_tuple ( sp, vec ! [ ] ) // i.e. `()`
732+ } else {
733+ unstable_marker
734+ } ;
735+ let expr = cx. expr_tuple ( sp, vec ! [ marker, expr] ) ;
736+
737+ MacEager :: expr ( expr)
738+ }
739+
635740pub fn expand_format_args < ' cx > ( ecx : & ' cx mut ExtCtxt , sp : Span ,
636741 tts : & [ ast:: TokenTree ] )
637742 -> Box < base:: MacResult +' cx > {
0 commit comments