-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
pure annotation - called CallExpression #2331
Comments
Yes, the second example above is definitely wrong. It apparently ignores the side effects of the outermost Are we in agreement that the first example above is fine, and the output of the second example should be the following?
Edit: fixed expected result above. @Andarist Just curious whether this bug was discovered in real life code. The reason being that |
This shows the issue more clearly:
This call should be removed:
It should output:
|
Aint sure, how to annotate all of them as pure then?
Not rly 🧌 I've written babel-plugin-annotate-pure-calls and was playing around with test cases when Ive discovered this. Would love yours (and @alexlamsl's too) review on that plugin. You guys are way more aware how uglifyjs works and how to 'properly' annotate the code.
Same thins as above - how to annotate each of this calls to have them all removed? ofc the problematic here is a call to Also I was wondering what should happen for: /*#__PURE__*/fn()()()() Is a single annotation enough to DCE whole thing? |
Good to know that this wasn't discovered in real life code. I was concerned that this came up as result of pure annotations in core Babel polyfills or something. I took a look at the sources and tests for
Multiple annotations and well placed parentheses could be used. When the pure annotation feature was added no consideration had been given to that fact that the outer
The intention was that the pure annotation would affect the outermost call only. For example the following output is logical and correct:
Although |
@alexlamsl What's your take on this issue? Should a pure annotated call ignore the side effects of |
@Andarist @kzc sorry for the radio silence – been running around town the whole day. So I think what happens is the call And if that is the case, then there are two locations to patch up, i.e. (I like how (I typed this on the phone in a metro heading home, so apologies in advance for any mistakes.) |
Actually, that is the topic for today. :-) @alexlamsl Implementation aside, do you think that side effects should be considered for I'm on the fence. I can see a case for either behavior. |
If we want consistent behaviour alongside I remotely recall in the distant past I tried to "fix" |
Just making sure this actually works: $ echo 'fn1(fn2(a()),b())(c());' | uglifyjs -c pure_funcs="fn1(fn2(a()),b())"
c(); |
I'm leaning in that direction. When you think about it, the behavior is somewhat arbitrary and is ultimately what we decide it will be. Let's just keep the behavior as is for now, but leave the ticket open while we consider the issues involved and see if it impacts any real world code. |
Another data point is that $ echo 'function f(){ var a=fn(); a(b()); }' | node bin\uglifyjs -c
function f(){fn()(b())}
$ echo 'function f(){ var a=fn(); /*@__PURE__*/a(b()); }' | node bin\uglifyjs -c
function f(){fn();b()} |
Indeed - let's collect more real-world code examples to sniff out potential corner cases. Especially since I can't think of any easy way to augment |
Change the [bug] label to [question]? |
off topic: Some projects are using uglify Angular Build Optimizer has been reported to significantly reduce app bundle sizes - by as much as 80%: angular/components#4137 (comment) "on angular.io, we saw a 52% size reduction in our production build."
|
And of course @Andarist added |
@kzc I believe what you have described here is correct and I still consider this thing a bug. In the same way as
I agree this is quite a corner case and possibly not the highest priority.
Well - its not targeted for the general use case. I believe (once i smooth the rough edges) it might be useful for side-effects free libraries such as
Non, but thats quite the point of the plugin. The user must want to use it and know its risks, I believe the risks (for well tested/battle tested plugin) are minimal though. The only heuristic I use is that a function call appears in an assignment context + I consider contents of the top level IIFEs as top level calls (this is a though a subject of taste and Im not strongly attached to the idea, so I might change it or provide an option for this).
Certainly agree that program flow analysis is difficult and I do not intend to touch the subject within that plugin too much, its job is to assume calls being pure (technically even not pure per se, but side effect free - or that if the result is unused any possible side effects are unused too and the call can be eliminated). I believe the test case is ok (i invite u ofc to further discussion about it), maybe the test case itself is not obvious, but lets consider slightly different input: a = (function() {
var b = (function() {
var c = (function() {
var d = call()
someOtherCall(d)
})()
})()
})() The test case shows that
Multiple annotations sounds like a good solution, but well placed parentheses cannot be achieved (afaik) with babel transformations, because AST doesnt hold any information about that.
It is a side effect in inner call expressions that gets ignored (but outer callee) not in the outer call expression. I use Babel's semantics here, maybe yours differs.
Agree 100%, but as mentioned - the whole statement gets dropped at the moment, which sounds to me like a bug. The only problem I see with the approach is that its unobvious how to annotate all those calls as pure (in /*#__PURE__*//*#__PURE__*//*#__PURE__*/fn()()() and when we go back to AST we end up with a single (outermost) CallExpression with 3 leading comments. Unfortunately such operations (when comments are involved) as going from ast to code and back to ast are not inverse operations (in mathematics terms).
@alexlamsl No worries, your answers were extremely fast.
wow, thats one weird looking syntax :o
what does
Wow again, I didnt expect such great results, Im wondering how structured are the apps that have benefited from such reduction. I think those optimizations that this project (Angular Build Optimizer) performs (in a way that PURE annotations have desired effect) are great! In example - class fold. I plan to do the same for babel's outputs (as discussed in other threads). |
@Andarist I appreciate that the pure annotation behavior with respect to ignoring side effects of As for |
Be aware that I may not read the re-edits of past posts.
The lack of output reversibility with respect to pure annotation comments is a known issue that will not likely ever be addressed. Comments make for a poor code annotation system - it's a hack. Uglify is rarely used as a code formatter - there are better alternatives. The code emitter would not necessarily be able to handle outputting pure annotations correctly in pathological Back to handling side effects of
Currently this results in both statements being entirely dropped. But if side effects of the call expression were considered it would emit:
That's not expected, nor desirable. We don't want to introduce problems for code in use in the wild. |
Totally agree.
Sure thing, I've created an issue for the review there.
Im not sure if I see this. Do you mean that getters could be called here? Isnt there an option to assume pure getters? |
Yeah, getters or unset globals like debug or log statements that would result in an exception being thrown. Or just to simply avoid littering the output with code that would not have been there with the existing pure annotation behavior. There is one change I'd like to put in - a new finer granularity compress option to control whether pure annotations are enabled: --- a/lib/compress.js
+++ b/lib/compress.js
@@ -93,6 +93,7 @@ function Compressor(options, false_by_default) {
unsafe_regexp : false,
unused : !false_by_default,
warnings : false,
+ __PURE__ : !false_by_default,
}, true);
var global_defs = this.options["global_defs"];
if (typeof global_defs == "object") for (var key in global_defs) {
@@ -2083,6 +2084,7 @@ merge(Compressor.prototype, {
});
AST_Call.DEFMETHOD("has_pure_annotation", function(compressor) {
+ if (!compressor.option("__PURE__")) return false;
if (!compressor.option("side_effects")) return false;
if (this.pure !== undefined) return this.pure;
var pure = false; That way in the event of a problem or bug it can be disabled without needing to disable Also, having a |
Might be useful, even though the proposed option name indicates quite clearly what it does I would prefer some better ("human-friendly") name. |
It was the first option name that came to mind. Alternatives: |
|
Another idea would be |
Or perhaps set a string value for |
Bug report
ES5 input
Uglify version (
uglifyjs -V
)3.1.1
The
uglifyjs
CLI command executed orminify()
options used.uglifyjs -mc passes=3,unsafe --toplevel
JavaScript input/output
It seems like a bug that by calling a CallExpression the inner unannotated call get eliminated.
The text was updated successfully, but these errors were encountered: