Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile time scss macro #163

Merged
merged 2 commits into from
Jan 7, 2023
Merged

Compile time scss macro #163

merged 2 commits into from
Jan 7, 2023

Conversation

Wicpar
Copy link
Contributor

@Wicpar Wicpar commented Jan 2, 2023

Created rsass_macro sub-crate for compile time usage of the library

@Wicpar Wicpar changed the title creates compile time scss macro Compile time scss macro Jan 2, 2023
LitStr::new(core::str::from_utf8( &rsass::compile_scss( input.value().as_bytes(), Format {
style: Style::Compressed,
precision: 10,
}).unwrap()).unwrap(), Span::call_site()).to_token_stream().into()
Copy link
Contributor Author

@Wicpar Wicpar Jan 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to properly handle the error, panicking the compilation is not necessarily good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe also add optional format

@Wicpar Wicpar mentioned this pull request Jan 2, 2023
@kaj
Copy link
Owner

kaj commented Jan 3, 2023

Yes, this seems useful! Thanks!

Two questions, since I'm new to creating procedural macros:

How would a crate that uses it look, should it use the rsass-macro crate directly? Or should there be a macros feature in the rsass crate that exposes it? That seems to be the more common way? But I guess the rsass-macro crate would still need to be published separatley?

I agree that any errors should be properly reported. How is that normally done, can the macro function return a Result? Or should it return successfully but with output that triggers a compiler error?

@kaj
Copy link
Owner

kaj commented Jan 3, 2023

According to the reference:

As functions, they must either return syntax, panic, or loop endlessly. [...] Panics are caught by the compiler and are turned into a compiler error. [...]

Procedural macros [...] have the same resources that the compiler has. For example, standard input, error, and output are the same that the compiler has access to. [...]

Procedural macros have two ways of reporting errors. The first is to panic. The second is to emit a compile_error macro invocation.

So a panic is ok, maybe it could be made more helpfull by writing rsass error message before the panic, or maybe the rsass error message could be the panic string. Or the same could apply with a compile_error!(..) output.

@kaj
Copy link
Owner

kaj commented Jan 3, 2023

Introducing an error in the test, I see that the error message looks surprisingly good as-is:

error: proc macro panicked
  --> tests/test_macro.rs:4:31
   |
4  |       const CSS: &'static str = rsass_macro::scss!(r#"
   |  _______________________________^
5  | |         .class {
6  | |             background: red;
7  | |             &:hover {
...  |
10 | |         }
11 | |     "#);
   | |_______^
   |
   = help: message: called `Result::unwrap()` on an `Err` value: blue is not a number.
             ,
           5 |                 background: min(blue, 1px);
             |                             ^^^^^^^^^^^^^^
             '
             - 5:29  root stylesheet

error: could not compile `rsass-macro` due to previous error

But using the syn::Error, i improved it to:

error: blue is not a number.
         ,
       5 |                 background: min(blue, 1px);
         |                             ^^^^^^^^^^^^^^
         '
         - 5:29  root stylesheet
  --> tests/test_macro.rs:4:31
   |
4  |       const CSS: &'static str = rsass_macro::scss!(r#"
   |  _______________________________^
5  | |         .class {
6  | |             background: red;
7  | |             &:hover {
...  |
10 | |         }
11 | |     "#);
   | |_______^
   |
   = note: this error originates in the macro `rsass_macro::scss` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `rsass-macro` due to previous error

... At least I think that's an improvement. :-)

It could be improved further by creating a rust Span from combining the rsass span in the error with the rust Span::call_site(), but that would probably be rather complicated.

My modified code for giving the above error format looks like this:

#[proc_macro]
pub fn scss(tokens: TokenStream) -> TokenStream {
    let input = parse_macro_input!(tokens as LitStr);
    let format = Format {
        style: Style::Compressed,
        precision: 10,
    };
    match rsass::compile_scss(input.value().as_bytes(), format) {
        Ok(output) => {
            let output = core::str::from_utf8(&output).unwrap();
            LitStr::new(output, Span::call_site())
                .to_token_stream()
                .into()
        }
        Err(err) => {
            let msg = format!("{err:?}");
            Error::new(Span::call_site(), msg)
                .into_compile_error()
                .into()
        }
    }
}

Edit: For the Error, input.span() may be used instead of Span::call_site(). That changes the error message, but I'm not quite sure if I consider it an improvement or not ..?

@Wicpar
Copy link
Contributor Author

Wicpar commented Jan 3, 2023

Or should there be a macros feature in the rsass crate that exposes it?

that's unfortunately not possible due to circular dependencies, An additional crate is required, be it a core crate, or a macro crate.

changing the PR to your code,

@Wicpar
Copy link
Contributor Author

Wicpar commented Jan 3, 2023

i have not done all that is necessary to publish the macro subcrate, i'm not quite familiar with that process.

@kaj
Copy link
Owner

kaj commented Jan 3, 2023

Or should there be a macros feature in the rsass crate that exposes it?

that's unfortunately not possible due to circular dependencies, An additional crate is required, be it a core crate, or a macro crate.

Ah, of course. And there has to be a separate crate since a single crate can't be both a regular lib crate and a proc-macro lib crate at the same time? (And rsass is currently both a regular lib and a bin crate.)

changing the PR to your code,

Ok.

i have not done all that is necessary to publish the macro subcrate, i'm not quite familiar with that process.

The first step would be include it in CI, but maybe I can experiment with that after merging. Also, the code is a bit crowded as it is (with the library, the cli, and a test updater in the same src dir), so maybe I should pull out those to separate crates as well, converting this git repo to a monorepo for those. But that is also easiest to think about after merging this, I believe.

But before that, are we happy with the macro api? Just taking a string as a single argument seems fine for the base case, but maybe we also want the format to be changeable, and maybe the a base path for any include files (@use, etc)? Maybe also a possibility to set variables from the macro call to use in the sass code? But then, I guess all those could be done "var-args" style, maybe with a $ sign for defining variables. If so, it should be possible to add all that later, without breaking compatibility for the case where the input is only a literal string.

@@ -0,0 +1,13 @@
### Rust template
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe amend the root .gitignore in the project instead of adding a separate one here?

proc-macro = true

[dependencies]
syn = { version = "1.0.106", features = ["full", "extra-traits"] }
Copy link
Owner

@kaj kaj Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These extra feutures don't seem to be needed? At least, I can run cargo test without them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i over required those. if it compiles they can be removed.

@Wicpar
Copy link
Contributor Author

Wicpar commented Jan 4, 2023

If so, it should be possible to add all that later, without breaking compatibility for the case where the input is only a literal string.

that was my logic. it can quite easily be done with syn, simply create a struct and implement the parse trait. parse the LitStr like it used to and then you can parse whatever you want, even param = value syntax.
My contributon is simply changes i made for myself, i don't have the time to get too in depth on the implementation.
You can change the PR as you like, but unfortunately i will not be able to deliver you the perfect PR that can simply be merged and published... This PR can be merged on another branch if need be, so all necessary changes can happen.

@kaj
Copy link
Owner

kaj commented Jan 7, 2023

I'll just merge this now, and then start a separate PR for integrating it in the workspace etc.

@kaj kaj merged commit 571f54d into kaj:main Jan 7, 2023
kaj added a commit that referenced this pull request Jan 7, 2023
Adds to #163
- Rename rsass-macro to rsass-macros, as I will add a second macro for including css from scss in a file.
- Add rsass-macros to the workspace.
- Add some documentaion.
kaj added a commit that referenced this pull request Jan 17, 2023
Released 2023-01-17.
See also <https://rasmus.krats.se/2023/rsass027.en>
Progress: 4604 of 6925 tests passed.

* Changed repo structure to a monorepo (PR #164).
  - The commandline interface now lives in the separate crate rsass-cli.
  - The test updater also have a separate crate, but not intended for
    publication.
  - Tests are restructured to fail faster for simple things, while
    macos and windows testing is added to the github action (appveyor
    is removed).
* Added macros (PR #163, #165).
  - The workspace now includes a `rsass-macros` crate that can be used to
    compile sass to css strings at compile time in rust crates.
* Changed numeric handing (mainly conversions to/from `f64`) to match
  improvements in how dart-sass handles numerics.  This is mainly done by
  removing some special cases as dart-sass and rust now agrees on more of
  those.
* Added new variables `$epsilon`, `$max-safe-integer`, `$min-safe-integer`,
  `$max-number`, and `$min-number` in `sass:math`, matching recent dart-sass
  additions.
* Added new `split()` function in `sass:string` module.
* Changed the span type used in parsing to a local type that borrows a
  `SourceFile` instead of `nom_locate` dependency (PR #158).
* Changed the type `SourceFile` to be reference counted, so cloned
  `SourceFile` objects share the same actual data (PR #158).
* Moved the `SourcePos` type into the `input` module and converted it from
  keeping a copy of the relevant line to keeping a range with a (reference
  counted) `SourceFile` (PR #158).
* Changed css creation from just writing to a text buffer to building
  a tree representation of the css (and serialize it to text as a
  final step) (PR #159).
* Changed `BinOp` value in both sass and css from a tuple variant with
  boxed values to a single boxed struct variant.
* Improved value checking; Report an error if trying to output invalid
  css values in some cases. Also, arithmetic involving colors that
  used to be calculated is now correctly invalid (PR #161).
* Changed `css::Item::AtRule` to wrap the new type `css::AtRule`.
* More varaints of `Invalid`, slightly fewer stringy errors.
* Changed handling of `hue` arguments to color functions, to allow
  different angle units, matching updates in sass-spec.
* Minor correctness improvent in the `calc` function handling.
* Clippy now takes MSRV from Cargo.toml.  Slightly more recent lints allowed.
* Updated clap to 4.0 for the command-line interface.
* Updated sass-spec test suite to 2023-01-06.

Thanks to @Wicpar for the initial macros implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants