And sweet.js macros can be used to implement the type system of your choice in otherwise stock Javascript. (For that matter, you could un-overload the + operator so that it only works with numbers, and invent a . operator to do string concatenation.)
I can see a potential argument that doing type checking via macros costs more than precompilation in efficiency terms, but on the other hand, that's not necessarily so. How does TypeScript do type checking for non-constant values? That is, if I define function foo(bar: string) in a TypeScript library, then compile it, load the library in a Javascript interpreter, and call foo(Integer.parseInt("42")), what happens? I'm guessing that either foo() misbehaves, or that foo() throws an exception.
In the former case, there is no benefit to TypeScript because your type annotations are evanescent and misleading -- you can't rely on them, so you either have to write fences around input values just like you would without them, or risk your code misbehaving because it's written to rely on unreliable type annotations.
In the latter case, TypeScript is adding those fences automatically, so that a function which expects an integer argument can check whether it actually got one and fail if it didn't. That's basically what a macro system implementing type checking would do, too, and it seems to me that in both cases optimization would be merely an implementation detail.
It's not practical to implement a type system using macros. The problem is the software ecosystem. First of all, I don't think sweet.js macros can implement a full type system like TypeScript has which can load type definitions from other files, do type inference and support interface definitions.
Secondly, it's difficult to get a community around a single type system. Thirdly, it's difficult to get IDEs to support these sweet.js macro type systems.
The fact is, a sweet.js macro type system which is widely in use and supports everything TypeScript has and is supported by many IDEs doesn't support, and is unlikely in my opinion, if not technically impossible. In theory it might work.
If you compile TypeScript and then call it using JavaScript with the wrong type the code will misbehave. It's a downside, true, but a dynamic type checking system written in JavaScript would be too costly. Sweet.js macro type system wouldn't bring any benefit in that regard either.
You can rely on TypeScript type checking to make sure your code is correct. Similarly, if you develop in JavaScript, you can use unit tests instead of type checking to make sure your code is correct. Unit tests nor type checking can't guarantee that other code is correct, even if that other code happens to use your own code.
The benefits of TypeScript's type checking include the elimination of a class of unit tests, automatic refactoring, full intelli-sense support, better readability and automatic documentation (which doesn't remove the need of manual documentation).
You should have a look at Typed Racket [1] which is a type system for an existing language (Racket) built entirely with macros [2] that satisfies all of the criteria you want -- works with the IDE, safe interop with untyped code, etc.
There's also TypedClojure (https://github.com/clojure/core.typed) which is based on Typed Racket (is what I read in previous discussions about it here).
Maybe this will help see the full scope of the power of macros: macros receive their arguments as an AST (i.e. a list) and are free to transform it as they please; they also have full access to the host language and are free to implement a type system checker that walks code and checks that all the types match. There are no restrictions here - the type checker would be regular scheme code that would be wrapped into a macro simply to make it run at compile time. So there's nothing less powerful about macros at all. It's just code that runs at a different stage (compile-time instead of run-time). After macros, in terms of power, you have reader macros, that receive their arguments as raw text and are free to turn it into any AST.
> For that matter, you could un-overload the + operator so that it only works with numbers
No you can't. First of all, you can't implement operator overloading, because Sweet.js is quite limited, as the macros have the form:
<macro-identifier> <expression...>
Also, because you don't have the types when Sweet.js compiles the code, it means that you can't distinguish between numbers and strings or what have you.
Huh. This is what I get for talking something up before I've had a chance to play with it for myself -- you're right, Sweet.js doesn't allow for arbitrary syntax definitions, just those of the form keywordargumentsbody, with macro-expansion only on body, and no way of capturing the symbol prior to keyword.
I can still see a potential method of rewriting functions, whose arguments were decorated with type specifiers, to provide type checking. But that would involve prefixing the function body with type checking code to be evaluated at runtime, which would probably produce a monstrous increase in execution time, and would be ugly either way.
Well. I suppose I shouldn't have expected anything different, knowing that a real macro system requires support at the language level, and that a retrofit like sweet.js is perforce going to be limited in what it can hope to accomplish. But I find myself disappointed nonetheless that sweet.js is what it is, rather than what I wanted it to be -- and, I think, what a lot of people, not least of whom the author of the linked article, imagine it to be.
Actually sweet.js just landed infix macros [1] so you can match on syntax that comes before the macro name. This just happened last week so we haven't updated the docs or made an official release just yet.
Unfortunately this contribution is inhibited from being significant in value by the fact that TypeScript doesn't support full gradual typing [0] and has an intentionally unsound type system [1].
Accidentally unsound is a problem, but intentionally unsound just means that they balanced soundness against other design criteria such as completeness, complexity, and tooling capability, then decided that they were willing to trade soundness for other things.