Now, this really shouldn't be an issue unless someone is deliberately abusive. Java's generic types are also Turing complete[0], and it hasn't been problematic. Just because an abusive program exists doesn't mean it will be written.
But who am I kidding... this is the JavaScript community. I'll give it 2 weeks until someone releases a framework allowing you to write functions that don't compile unless their input is a prime number.
Sure, typescript program may never finish compiling, although it will still halt: Just `kill -9`. The halting problem solved! ;-)
Seriously though, there are many languages / systems that are 'accidentally Turing complete', including CSS + HTML, and it doesn't really impede them. See http://beza1e1.tuxen.de/articles/accidentally_turing_complet...
The problem is that you can't really tell when a long compile is an infinite or just a really long one. You want non-Turing completeness because your tools need to be reliable.
In most cases, you know something is wrong once your typescript compilation takes more than a few seconds. At most, it could be an interesting DOS attack, but most continuous integration systems should have a limit on how much seconds they can run the compiler for. So it's really not a problem if you have constraints on resources...
So, if not turing complete, what kind of automata would you use to accept your language, say if you were designing your own language?
If the type checker takes too long, sonething isn't right.
At least if your type checker weren't Turing complete, you know it will finish at some point.
Seems like a non-opinionated classification of "overrated".
Or we could go even more meta, since no physical system is Turing complete due to the Bekenstein bound, so any expressible system is necessarily at most an FSA.
AFAIK, CSS + HTML is not Turing complete, but CSS + HTML + "Human pumping tab and space" is, which makes infinite loops less of a problem.
Specifically, for any implementation of a TS compiler, there is some correct TS program that will never finish compiling.
Every program has a TS compiler that will compile it, but no compiler can work on all programs.
This is addressed by (1) not finishing in these cases or (2) imposing practical limits on the compilation and concluding failure if these are surpassed.
How would you correctly compile a program that has an actual infinite loop in the type system. Some equivalent of while (1) {}
For example, you could translate "the Turning program yields 1" as "the types are valid", and anything else (0 or nontermination) as "the types are not valid".
So "while (true) {}" would correspond to invalid types. And a sufficiently smart interpreter/runtime could conclude that "while (true) {}" does not yield 1, just as a sufficiently smart complier could conclude the type constraints are not met.
But no interpreter (and no compiler) would be able to reach a correct conclusion in all cases.
Honestly that seems like it could be pretty useful for making sure that "correct" data is passed at compile-time...
The halting problem says that you can't write a halting decision procedure that takes an arbitrary program as input and determines whether that program halts, for any program. That doesn't mean that no programs halt. On the contrary, most programs in Turing complete languages obviously do halt and it's often trivial to tell if a particular program halts or not (e.g., `print "hello"` obviously halts). A Turing complete language can compute anything that is computable. So if the type system is Turing complete, you should be able to type a function that only takes primes... in theory.
runtime vs compile time. For example someone could write some TypeScript that would forever compile, which is intuitively unexpected when compiling.
I think the more motivating reason why this might be a bad thing is it implies the type system is pretty complex and that complexity can be a problem. When users are reading and writing programs, they are running the type system in their head to decide what code to write and reason about how it behaves. If you can create types that cause arbitrary computation to be performed, then you're forcing the user to run a pretty complex simulation in their head.
Worse, unlike the dynamic runtime behavior of their program, they need to do that reasoning before they can even begin to run their program. It's not like "well, sure, you can maybe get the program into a weird state". It's "you won't be able to even run the code at all until you understand and fix all the type errors."
Given that, it's important to keep the type system simple where you can. The challenge is balancing that with the need to make the type system sophisticated enough to not prevent users from writing programs that are dynamically correct. All type systems are inherently pessimistic — there are programs you can write that would do the write thing but where the type system can't prove that so it generates a "false positive" error.
A few of those are tolerable. Most users don't seem to mind adding a seemingly pointless cast here and there, or reorganizing some already-correct code to please the checker. But if they have to do that too much, they start to feel like the language is stupid or getting in their way. See, for example, the perennial complaint about the lack of generics in Go.
That said, there's some contention in that thread over whether or not Turing completeness has been demonstrated.
In general, having a turing complete type system isn't a major issue, many languages have that property.
When speaking of a general-purpose language you usually want it to be Turing complete. However, you may not want a single subsystem of that language to be Turing complete on its own. That's a lot of complexity for one thing.
Another reason you might not want a Turing-complete language inside your language is it'd be possible to write type declarations that do things semantically separate from the rest of the code. If you're looking at a program you typically think the only logic embedded in the types is constraining the way data is used in the program. All the code to actually do things with the data is elsewhere. If you have a type system that is by itself Turing complete, it could do any calculation someone wanted to encode into it without assistance from the rest of the source code. That's potentially a security nightmare to audit.
Arbitrary code execution is the holy grail of exploits. If the type system is Turing complete or even nearly so you can encode arbitrary behavior into the types without using any of the language's mainstream operative syntax. One could put an entire subsystem of the running program into what looks like descriptive, conscriptive statements about the data the operative syntax is to use. This is ripe for hiding a source-level trojan in a part of the code that needs little to no support from the rest of the program. When you're first looking at code from others, is your first instinct before compiling it to check deeply in the types defined to make sure there's nothing untoward going on semantically?
Looping forever is a very simple DoS or a very complex mistake. It would also be very simple to end the compilation run and investigate. Intentionally obfuscated malicious code would likely be able to pass unnoticed much longer and do much more damage. The halting problem, realistically, is a problem but is the least of your problems here. It's already quite easy to write a non-halting piece of code on purpose and it's possible accidentally.
The big problem is the unexpected computational power of this sort of thing being abused, not that it's accidentally going to take forever to compile. Nobody's going to die of old age watching an uninterrupted compilation.
This whole argument is nothing more than equivocation: the phrase "arbitrary code execution" means one thing in the security sense but it means something completely different when you are talking about theory of computation.
A Turing machine can execute "arbitrary code" in the sense that it can evaluate any computation. That does not mean that it can effect arbitrary changes to a host computer emulating a Turing machine. That's why sandboxing works. Try writing an HTTP server with a Turing machine: you can't, because Turing machines have no facility for networking. Similarly, just because a type system lets you execute arbitrary code doesn't mean that you can write an exploit in that code, you would need some exploitable endpoint accessible from the type system.
Or in short, just because you can simulate a Turing machine on someone else's computer doesn't mean you can take over their computer.
And yes, nobody's going to die waiting for compilation, but if you can DoS somebody's CI server by making a change to an upstream module which makes compilation take forever, maybe your victim will be pretty unhappy about that.
What I said was that if you are able to hide arbitrary computation of an obfuscated form into what looks like a bunch of innocuous type definitions then run a program that takes over from that point, you can potentially hide malicious code in the type declarations before the program proper even begins to run.
A Turing complete type system can theoretically change the initial state the rest of the compiled code starts under.
Picture this: you clone a repo. There's a file that contains operative code in some paradigm (non-OO imperative, OO imperative, non-OO functional, OO functional, logic, whatever). There's another file that's just types used by that first file. How much attention do you honestly pay to the types before looking at the operative code and then compiling and running it? Now, let's assume someone's found a bit of a bug in your compiler that lets this Turing-machine equivalent type system change the memory addresses of some structures or fill certain parts of memory with machine language. After all, you've not written your compiler to check everything a Turing complete language might do. Then the operative code you're counting on, that you've assumed starts with a clean slate other than the types assumed to be straightforwardly declared, copies that memory somewhere, or seeds a state machine or eval with it.
A Turing complete language means you can do anything any other Turing complete language can do. And having a Turing complete type system without making that clear to language users potentially means it can be obfuscated and effectively hidden. In TypeScript we're talking about a Turing complete subsystem (the type system) that feeds into a Turing complete system (the rest of the language) that translates into another Turing complete system (JavaScript), that gets compiled down to another Turing complete system (the JIT code for the VM). In all that complexity there be dragons.
You might not be familiar with the concept of multi-layer attacks or think they're unlikely to work, but that doesn't mean they don't exist or aren't used in the field.
Adding time limits radically changes things.