Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Normalize free interpolation in SassScript #1778
Spurred by #1774, I started thinking about how messy interpolation is in SassScript at the moment and how to clean it up. This issue is the result of that thought process. I'm especially interested in hearing what other implementors have to say, but community members are welcome to chime in as always.
Long ago, when only the indented syntax existed, SassScript couldn't be used directly in property values. In order to give properties dynamically-generated values, they had to be interpolated using
Unfortunately, working "more or less anywhere" was a parsing nightmare, and the specifics of where interpolation can be used and its effect on the surrounding script are bizarre and arcane. @chriseppstein and I want to fix that by substantially limiting the places
I propose that, as today,
Here are some examples (I'm including quotes for unquoted strings in the output to clarify their extents):
The primary question when figuring out how to handle this was how much interpolation should be restricted. @chriseppstein and I agree that interpolation in SassScript reads strangely in many situations, but we ended up deciding to continue allowing it in most places. One major reason for this is backwards-compatibility: no matter what we do, the process of making this change will be painful, and any functionality we can preserve will help mitigate that pain. But there were also compelling use cases for retaining interpolation in various situations.
Interpolation in unquoted strings
It was tempting to restrict interpolation for use only in quoted strings. Interpolation in unquoted strings can be mimicked using
Interpolation outside of strings
The other big decision was whether to allow a bare interpolation expression that wasn't attached to any string at all. Both of us were fine with deprecating this until we remembered one situation where it's by far the best solution: a slash delimited. Right now when users want a slash delimiter for the values of properties such as
We considered coming up with a new way to produce a literal slash without using interpolation, but we didn't find anything that was clear enough to warrant the migration cost for all the stylesheets using the current method. In the end, we decided that since the current method looks pretty decent and can work with a more reasonable definition of standalone interpolation, we would leave it as-is.
Any change we make here will be backwards-incompatible. Since interpolation is such an old feature, we have to be very careful to only surface deprecation warnings to people whose stylesheet semantics will actually change (or as close as possible), and to provide them with actionable ways to fix those stylesheets. This is complicated by the fact that the effects of this change are difficult to reason about locally; an expression like
I haven't fully thought through how to handle the deprecation, but a set of heuristics seems like a good place to start. First, let S1 be the value of an expression containing interpolation under the old rules, and E the value of the same expression, to the extent that S1 covers. Let S2 be the conversion of E to CSS.
For example, suppose the expression in question is
Obviously this requires a more explicit notion of how to detect when S1 and S2 are CSS-semantically identical, and how to tell which operations would be a problem in the second case. But I think it's a good starting point.
Thanks for the write up @nex3. Apologies for the delayed response, I initially responded via chat.
LibSass does not have the same legacy baggage as Ruby Sass, and we also do not have a specific SassScript parser. Given a clear set of rules and updated Sass specs we should be able to move quickly on this.
I think this is a great opportunity for the Ruby Sass team to trial developing against Sass Spec, thoughts?
In service of determining how to go about deprecating the current semantics of SassScript interpolation, I want to precisely define them. @xzyfer, this may help with libsass compatibility as well.
For our purposes, we only care about free interpolation—that is, interpolation outside the context of a string or a special function (e.g.
The grammar for interpolation is straightforward. Note that the representation below elides much of the unrelated complexity of the SassScript grammar. The
The complexity lies in how this representation is evaluated. Because the semantics of string interpolation is already clear, I'll describe the evaluation of free interpolation in terms of its equivalent string interpolation (or "ESI" for short). To clarify that the strings returned by the ESI should be unquoted, I'll use backticks instead of double quotes to delimit them (so
The ESI for an
Similarly, for a
Now that we (hopefully) have a clear idea of how free interpolation works right now, we can start figuring out the surface area that needs deprecation warnings when moving to the new semantics.
Ideally, we want to warn only when the new semantics will produce semantically different CSS output. In practice determining this exactly isn't always feasible, since free interpolation produces values that can be used in many heterogeneous ways, so instead we'll warn if the values they produce are ever used in a way that will change behavior under the new semantics.
There are some expressions containing free interpolation whose values under both the old and the new semantics are CSS-equivalent strings. These include expressions where there are no operators (which also means no implicit list operators), as well as expressions with operators that will produce strings with identical semantics.
There are also expressions whose values have different stringifications under the old and new semantics. The stringification of an expression is the string representation of the result of its evaluation—for example, the stringification of
The following operators and their inverses should produce warnings immediately. Note that any expression containing free interpolation whose new ESI contains these operators should have an immediate warning, even if they also include other operators.
Finally, there are expressions that produce values with the same stringifications but different types under the old and new semantics. In practice the only operators that fall into this category are the list operators,
Most of the time, these values are benign. As long as they're included directly in the stylesheet without any further manipulation, they'll have the same behavior under the old and new semantics. But if they are manipulated in a way that won't work for the new value, that's a problem and we need to issue a warning.
Fortunately, the only way such a manipulation can occur is by passing the value to a built-in function that will treat it differently as a string than it will as a list. When passing an interpolation value produced via a list operator to such a function, the implementation should emit a deprecation warning. Of the canonical Sass functions, this includes:
It's up to each implementation to determine whether to emit warnings for which user-defined functions.
@nex3 Sorry, I don't see how this deprecated behavior would work. How does the user silence the deprecation warning? By changing to a string with interpolation? How does the code interoperate between both values? It seems like there's information loss.
Any expression that produces a deprecation warning can be converted to an expression that will produce the same value and will work under the new semantics by taking the ESI, making the quotes explicit, and wrapping it in
I just found one additional case where the new behavior differs from the old, and it's a doozy. It comes up when a dynamic value is included in an interpolated string without an explicit
What this means is that the ESI is no longer actually equivalent in all cases, because any of the newly interpolated values may or may not be a quoted string. We can detect this in simple cases like the first example, but not in general.
Hopefully, not too many people are relying on cases we can't detect in practice. I think we should still move forward with the deprecation and accept that our heuristic isn't perfect, but I wanted to put this out there and get people's opinions.
Unfortunately I've never used or written any sass myself, that's why I've not yet responded to this as I therefore have no preference about the syntax. From a libsass implementation standpoint, I wonder how this will influence nested interpolations. The biggest issues I had with implementing interpolations in libsass was with backslash escaping logic (also in regard to nested interpolations). I also struggled to detect when to unquote a resulting interpolation and when not to.
added a commit
Nov 7, 2015
referenced this issue
Nov 13, 2015
referenced this issue
Nov 17, 2015
I've run into another case that needs to be considered here: expressions that are adjacent to interpolation without any whitespace intervening.
Under the new rules, some of these would be parsed as interpolated identifiers while others would be parsed as space-separated lists.
The second, third, fifth, and sixth examples should produce deprecation errors; the first and fourth should not, as their stringifications remain the same.