-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Typesafety: assert function to fail at compile time #2747
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
Comments
Notice I tried to use this hack to declare js functions in less without success (inside a guard) |
Another idea could be this kind of syntax: .mixin (@a) when (lightness(@a) >= 50%) {
background-color: black;
}
.mixin (@a) when (lightness(@a) < 50%) {
background-color: white;
}
.mixin (@a) {
fail("This mixin can't be called with this value",@arguments);
} |
Ok so I think @seven-phases-max has an answer for everything :) As far as I understand I can provide custom functions by using a less plugin. Here is a List plugin from which I can inspire myself to build some kind of "less-assertions-plugin" |
This might be related to #1459 . It is not exactly the same thing, but has similar goal. |
I think it's easier to introduce some "assert" function and use it like: @unused: assert(@condition, "doh!"); instead of inventing some special syntax. Taking the first use-case into account, |
Yes a built-in assert would permit to reuse existing functions that do not throw! Could we do something like this? .mixin (@list) when (assert( lengt(@list) < 3 )) {
...
} |
@seven-phases-max I've done some tests and it seems that less can support quite complex expressions in a when clause, so definitively some simple assertion functions would be a huge win! However I'm still interested in a case to make less compilation simply fail anyway. See for example how ScottS here had the idea to call a mixin that does not really exist to make less fail (option 3): http://stackoverflow.com/questions/20959298/enums-documentation-with-less-mixins/21052115 Example where this can be needed is when there is a simple case for which you can to assert. .complexMixin(@list,@list2) when ( sin(length(@list)) < cos(length(@list2)) ) {
// not fail
}
.complexMixin(@list,@list2) when ( sin(length(@list)) > cos(length(@list2)) ) {
// not fail
}
.complexMixin(@list,@list2) {
fail("the cos and sin can't be equal!");
} I mean I want to be able to express switch / pattern matching expressions where the default unhandled case should never happen (pattern often used to fail fast): switch (value) {
case "x" ...
case "y": ...
default: throw new Error("fail!");
} |
So I tried to make a plugin but not really successfully :'( not sure to understand how to make it work and even how to make unit tests. It seems the test does not find my new function: |
Well, your mistake there is that you assume that Less For your particular example, however, it's easier to write just a error: function(message) {
throw {
type: "Whatever",
message: message.value // note the message arg is of type less.tree.Quoted (not JavaScript String)
};
} and then use it like: .complexMixin(@list, @list2) when (sin(length(@list)) < cos(length(@list2))) {
// ok
}
.complexMixin(@list, @list2) when (sin(length(@list)) > cos(length(@list2))) {
// ok
}
.complexMixin(@list, @list2) when (default()) {
unused: error("the cos and sin can't be equal!");
} |
ok thanks for the explanation :) that seems not very better than the "mixin does not exist" than ScottS proposed on stackoverflow unfurtunatly |
That was your code not mine :) For your initial example me would start with something like: .background-clip(@value) when
(at(l(border-box, padding-box, content-box, inherit), @value) = ~'') {
// ...
} then it's not a problem to derive a function to remove redudant |
Btw., thinking of the initial example yet more I recall one more method (w/o bothering with custom functions) to generate a bit more readable error message with a bit less verbose code (if compared to the method suggested in the SO Q above): [1]. |
Using the pattern matching mechanic on an inner mixin; clever! |
@seven-phases-max E.g. .complexMixin(@list, @list2) when (sin(length(@list)) < cos(length(@list2))) {
// ok
}
.complexMixin(@list, @list2) when (sin(length(@list)) > cos(length(@list2))) {
// ok
}
.complexMixin(@list, @list2) when (default("the cos and sin can't be equal!")) {
// will never be hit, because the guard will throw with the specified error.
} If you attach this logic to the You can generate really nice error messages with line numbers set to the correct location that way. |
@rjgotten Smart. I like it. It would allow library authors to enforce type safety and numerical ranges for mixins. That said... The error where it's being generated isn't really the intended error location. That is, if you want mixin A to fail based on some criteria, you aren't defining it at mixin A. You're defining it at mixin B, which only happens to be encountered because the lack of match at mixin A. But mixin A and B have no real relationship other than they match a mixin call. (So they're both related to call X, but not directly to each other.) So it seems to me what a developer REALLY wants is to have a failure at the location of a mixin, but that is illogical based on the concept of mixins. A non-match of a single mixin is not a "failure" at that point in the evaluation. So it seems to me, echoing some of @seven-phases-max's statements, only functions serve this purpose. A function has a single entity (per scope) so they can establish a pass/fail on parameters. Mixins can't. They can only establish a pass, and not an exception. Some plugin features in development (awaiting review) should make this a lot easier (via JS), and it's not out of the question that we could establish a native Less-based function syntax that allows these assertions down the road. |
Ideally, you'll want argument errors to pop at the call-site, i.e. , the location where the mixin was being called: // This is where we accept (one possible set of) valid parameters
.foo(@num) when (isnumber(@num)) and (@num >= 0) {}
// This is where we set-up the error to throw, when no matched
// signature of the `.foo` mixin managed to pass the guards.
.foo(@num) when (default("Not a positive number")) {}
// But this mixin **call** is what will throw the actual error, complete
// with line number, filename, etc.
.foo(-10); Is this also what you're trying to refer to? This means you attach the throw logic to There's a second reason you'd want the error to be thrown from the call node: orthogonality. |
Or both? In a call stack you'd have both. I wonder if it might be possible to generate just a light stack with the normal line / column of the error (from the call), and what mixin is returning that error? But yes, there is logic to what you say, and you're right that that's where errors are typically generated, so walking stuff back, maybe your proposal works after all. Btw, I assume you meant to write:
? |
No. I meant for the |
@rjgotten Oh right right that makes sense. Duh. @seven-phases-max Your thoughts? |
I like the semantics, nice spot!. Though due to |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Hi,
What I'd like to do is something like:
When the
background-clip
is called with a wrong value, instead of ignoring that value, it could be nice to make the compilation fail.Is it currently possible? or could it be in the future?
It would be really great to have a syntax to tell that the guard is a "compilation guard" and not simply "ignore-guard"
One simple solution i can think of is using a function that may fail. Something like:
The idea is that the function acts like an "assertion" that may make the compilation fail.
As far as I see there are already such functions that can make the compilation fail.
It would be nice to have more compilation safety with a oneOf function.
There are probably other useful options, like ensuring a list contains only know values.
For example when dealing with interaction modes, which can generally be mouse and/or touch. We have to handle special cases because the :hover is not possible on devices with only touch support.
The
contains()
function would be useful there :)Maybe we could also pass as mixin arguments complex json objects and assert in functions that they have a certain "shape"
I don't know which functions would be useful exactly but I think it would be a nice progress for less to have more "typesafety".
The text was updated successfully, but these errors were encountered: