I'm sure that's possible in other languages, but the program I was trying to compile wasn't that complicated... It had just been written by someone who believed every concrete class needed an abstract interface it was implementing.
Here for example: https://godbolt.org/z/4nzPY3h57 javac doesn't even bother to resolve that static field at compile time, which wouldn't even have observable side effects. Nope, just blindly emits a static initializer to invoke square(2) at runtime.
Or for a real fun one, enums: https://godbolt.org/z/xjbYvc8dx
Now some of that is necessary to handle all the stuff other classes could do with the enum, but some of it is just unnecessary. Like building the values array, which invokevirtuals all the ordinals of all the enum values. Ordinals the compiler already knew, since it's literally in the same .class file further up (and these are always in the same .class file, they are in lock-step, so there's again no observable difference if the values array was defined with constants instead of invoking ordinals on each field).
And then the trivial switch statement there isn't optimized at all, either, which again wouldn't be observable (compare against what eg. gcc would produce: https://godbolt.org/z/414rGjbE1 )
Further note that these examples are both static initializers, which means the JIT never "fixes" these (and thus instead perpetually contribute to slow startup). So if javac was going to bother with any attempt to optimize at all, you'd think it'd be here.
Bad bytecode is the kludge of byte code you get when you try to write the same thing in kotlin: https://godbolt.org/z/9sGa6csa6
And the bytecode is of comparable quality to javac's.
I remember there was some simple way to crash a Haskell compiler with an out of memory error by defining a series of types, where each successive type required twice as much memory as the previous type to represent.
It's a real problem.
Anecdotally: I maintain a Debian package with a large-ish (but far from huge), template-heavy C++ codebase. I had to give up on compiling on 32 bit machines long ago, simply because not enough addressable memory was available to the compiler. I know other maintainers have to heavily tune the compiler to work around the problem, too.
I was once asked by the Ubuntu folks to disable parallel builds altogether, due to even their 64 bit builders running out of memory altogether.
And this package is just large-ish – it's nowhere near truly large (like a browser or an office suite).
Operators are static methods so why is that surprising or nasty?
Just take a look at the rules for method overloading in C#, or take a look at one of the various C# compilers to see how methods are resolved.
Most people are really bad at estimating the implementation complexity and tradeoffs inherent in various language features. I'd say that C++ serves as a warning to others... think before you add features to your language, or you'll end up a total mess, like C++. Java and C# gave us some interesting and subtle lessons about what does and does not work about language features like generics. C++'s templates are just a total mess, all around. Java's generics are kind of a funny compile-time feature and have a ton of limitations. C# generics have some downright nasty interactions with other parts of the type system, like operator overloading.
IIRC the designers of C# have some regrets about how generics and other features were implemented. This is from an in-person talk, I don't have anything to cite.
You should take almost nobody at face value when they talk about language features like generics, because there is almost nobody with direct experience both designing and implementing those features. You should be pretty skeptical about what I'm saying too, IMO. But do believe that the design tradeoffs for generics are a complicated enough to justify the wait.
The Golang approach seems to strike a balance between extreme approaches. The C++ approach is "pay for what you use" which, in practice, is actually an extremist language design philosophy. In C++ / Rust, you are supposed to pay for templates only in code size and not in runtime. Everything is fully instantiated. The Haskell approach is "one abstraction fits all, everything is a pointer, use an implementation dictionary, monomorphization is an optimization". Again, something of an extremist approach (similar to Java's, but the comparison with Go / Haskell is better because Go / Haskell both use implementation dictionaries).
A middle approach is actually somewhat novel, believe it or not.