├── .gitignore ├── Cargo.toml ├── README.md ├── src └── lib.rs └── tests └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "overload-strings" 3 | version = "0.1.1" 4 | authors = ["Georg Brandl "] 5 | license = "MIT/Apache-2.0" 6 | description = "Haskell OverloadedStrings for Rust (using Into)" 7 | repository = "https://github.com/birkenfeld/overload-strings" 8 | 9 | [lib] 10 | plugin = true 11 | 12 | [dependencies] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # overload-strings 2 | 3 | This is a quick and silly syntax extension, mostly to familiarize myself with 4 | compiler plugins using custom attributes. It does the equivalent of Haskell's 5 | `OverloadedStrings`, i.e. it inserts an `.into()` call onto every string 6 | literal. No extra trait is necessary. 7 | 8 | ## Usage 9 | 10 | As a compiler plugin, requires nightly Rust. Add 11 | ```rust 12 | #![feature(plugin)] 13 | #![plugin(overload_strings)] 14 | ``` 15 | then apply the `#[overload_strings]` attribute on the item(s) you want to 16 | overload string literals in (module, fn, impl, ...). 17 | 18 | The annotation does *not* automatically recurse into submodules, to keep 19 | surprises due to nonlocal effects down. It also ignores `static`s and `const`s, 20 | because they cannot contain method calls. 21 | 22 | Where the ambiguity leads to errors in type inference, you can use the 23 | `type_ascription` nightly feature to disambiguate. 24 | 25 | Now you can call functions expecting `String`, `Cow`, and all other types 26 | that implement `From<&str>` with a string literal: 27 | 28 | ```rust 29 | struct Person { 30 | first: String, 31 | last: String, 32 | birthplace: Cow, 33 | } 34 | 35 | process_persons(&[ 36 | Person { first: "Doug", last: "Piranha", birthplace: "London" }, 37 | Person { first: "Dinsdale", last: "Piranha", birthplace: "London" }, 38 | ]); 39 | ``` 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin_registrar, quote, rustc_private)] 2 | 3 | extern crate rustc_plugin; 4 | extern crate syntax; 5 | 6 | use rustc_plugin::registry::Registry; 7 | use syntax::ast::{Expr, ExprKind, Item, ItemKind, LitKind, Mac, MetaItem, MetaItemKind}; 8 | use syntax::codemap::Span; 9 | use syntax::fold::{self, Folder}; 10 | use syntax::ptr::P; 11 | use syntax::ext::base::{Annotatable, ExtCtxt, SyntaxExtension}; 12 | use syntax::parse::token; 13 | 14 | const ATTR_NAME: &'static str = "overload_strings"; 15 | 16 | struct StrFolder<'a, 'cx: 'a>(&'a mut ExtCtxt<'cx>, Option); 17 | 18 | impl<'a, 'cx> Folder for StrFolder<'a, 'cx> { 19 | fn fold_item_simple(&mut self, i: Item) -> Item { 20 | // don't double-overload in case of nested annotations 21 | if i.attrs.iter().any(|attr| { 22 | match attr.node.value.node { 23 | MetaItemKind::Word(ref name) if name == ATTR_NAME => true, 24 | _ => false 25 | } 26 | }) { return i; } 27 | // ignore statics/consts, don't automatically recurse into submodules 28 | match i.node { 29 | ItemKind::Static(..) | ItemKind::Const(..) => { 30 | i 31 | } 32 | ItemKind::Mod(_) => { 33 | if let Some(top_span) = self.1 { 34 | if i.span == top_span { 35 | return fold::noop_fold_item_simple(i, self); 36 | } 37 | } 38 | i 39 | } 40 | _ => fold::noop_fold_item_simple(i, self) 41 | } 42 | } 43 | 44 | fn fold_expr(&mut self, e: P) -> P { 45 | if let ExprKind::Lit(ref l) = e.node { 46 | if let LitKind::Str(..) = l.node { 47 | return quote_expr!(self.0, $e.into()); 48 | } 49 | } 50 | e.map(|e| fold::noop_fold_expr(e, self)) 51 | } 52 | 53 | fn fold_mac(&mut self, mac: Mac) -> Mac { 54 | // this usually panics, but in our case it's ok 55 | fold::noop_fold_mac(mac, self) 56 | } 57 | } 58 | 59 | pub fn insert_str_into(cx: &mut ExtCtxt, _span: Span, _mi: &MetaItem, 60 | a: Annotatable) -> Annotatable { 61 | match a { 62 | Annotatable::Item(i) => Annotatable::Item( 63 | StrFolder(cx, Some(i.span)).fold_item(i).expect_one("expected one item")), 64 | Annotatable::TraitItem(i) => Annotatable::TraitItem( 65 | i.map(|i| StrFolder(cx, None).fold_trait_item(i).expect_one("expected one item"))), 66 | Annotatable::ImplItem(i) => Annotatable::ImplItem( 67 | i.map(|i| StrFolder(cx, None).fold_impl_item(i).expect_one("expected one item"))), 68 | } 69 | } 70 | 71 | #[plugin_registrar] 72 | pub fn plugin_registrar(reg: &mut Registry) { 73 | reg.register_syntax_extension(token::intern(ATTR_NAME), 74 | SyntaxExtension::MultiModifier(Box::new(insert_str_into))); 75 | } 76 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin, type_ascription)] 2 | #![plugin(overload_strings)] 3 | #![allow(dead_code)] 4 | 5 | pub mod fns { 6 | use std::borrow::Cow; 7 | 8 | pub fn takes_string(s: String) -> String { 9 | s 10 | } 11 | 12 | pub fn takes_str(s: &str) -> String { 13 | s.to_owned() 14 | } 15 | 16 | pub fn takes_cow(s: Cow) -> String { 17 | s.into_owned() 18 | } 19 | 20 | pub fn takes_into_string>(s: I) -> String { 21 | s.into() 22 | } 23 | } 24 | 25 | #[overload_strings] 26 | mod foo { 27 | use fns::*; 28 | 29 | static DONT_TOUCH: &'static str = "foo"; 30 | const DONT_TOUCH_EITHER: &'static str = "foo"; 31 | 32 | #[overload_strings] // the duplicate annotation is ignored 33 | pub fn concat_it() -> String { 34 | takes_str("Hello") + 35 | &takes_string(", ") + 36 | &takes_cow("World") + 37 | // this one can't be inferred, so use ascription 38 | &takes_into_string("!\n": &str) 39 | } 40 | } 41 | 42 | #[overload_strings] 43 | mod bar { 44 | mod sub { 45 | use fns::*; 46 | 47 | fn bar() { 48 | // this compiles because we don't recurse overloading into submodules 49 | takes_into_string("..."); 50 | } 51 | } 52 | } 53 | 54 | #[test] 55 | fn test_main() { 56 | assert_eq!(foo::concat_it(), "Hello, World!\n"); 57 | } 58 | --------------------------------------------------------------------------------