I made it up, although maybe I’m not the first? The third should actually be “language not designed around static analysis”
- You can write a static analysis that is fast and straightforward if you design the language around it (fast + easy + !language)
- Otherwise, you’ll run into features that prevent that. Ex (stolen from an above comment): name resolution within unqualified imports
- For many such features, you can write a brute-force solution. Ex: scan all possible definitions within the unqualified import until you find the target name (!fast + easy + language)
- Or you can optimize this solution, using incremental cached lookup tables (fast + !easy + language)
Of course, there are languages with features that make most static analyses for them neither fast nor easy (ex: for name resolution, eval or macros). But it holds in many cases, ex:
- Parsing: basic syntax (!language), GLR parser or LL parser with big alternatives (!fast), IELR parser or LL parser with smart alternatives (!easy)
- Type inference: many type annotations (!language), brute-force enumerate candidates (!fast), Hindley-Milner or bidirectional, possibly extended for features like trait resolution (!easy)
- Allocation: manual (!language), put everything on the heap (!fast), heuristics, implicit boxing and unboxing (!easy)
There’s a trend (I also don’t know where it originates) that most “data processing” problems can be solved with an easier slower or trickier faster algorithm (hence, competitive programming). Static analyses are this class of problem, but for them (sometimes) there is a third option: simplify the problem by putting hints in the language.