Reputation: 4481
As mentioned that typescript enables static typing, and as you know, type checking in compilers occurs with early binding or late binding paradigm. I think typescript's transcompiler uses something like early binding style. As we know, for completing the type checking, compiler should add some extra codes to the generated objects for checking the types during runtime. As I see, typescript doesn't add extra js codes for checking types during runtime and this style made some problem for our team. for example in below code the logical result of calling m method should be 1312
or some exceptions like ClassCastException in java. but the result is something else:
function m(a:string){
console.log(a+12)
}
var a:any=13
m(a as string) // result is 25
Should I replace typescript in our project with js because of this problem?
Upvotes: 0
Views: 938
Reputation: 164139
Typescript isn't a library, it's a language, it does not come with extra features (just pollyfil here and there), because of that there's no type enforcement at runtime.
That being said, your example doesn't do justice to the typescript complier, because if you do this:
var a: number = 13;
m(a as string);
You get an error: Neither type 'number' nor type 'string' is assignable to the other
.
Even with a more complex example:
interface A {
a: number;
}
interface B {
b: string;
}
function echoNumber(a: A): number {
return a.a;
}
console.log(echoNumber(<A> { b : "str" }));
You get the same error.
So typescript works, and it works great, but you're just not using it right (at least in your example) because if you use any
then you just might as well work directly in javascript, the whole point of typescript are the types and not the use of any
(only when you must).
Upvotes: 3
Reputation: 16292
You are confusing .toString()
with as string
.
The error you are getting has nothing to do with TypeScript. It is a JavaScript error. If you add a number
with a string
that can be cast to a number
, JavaScript will cast the string
to a number
and add them.
You can solve this problem by simply using the correct types:
function m(a: string){
console.log(a + "12")
}
var a: string = "13".
m(a) // result is 1312
var b = 13.toString();
m(b) // result is 1312
var c = 13;
m(b.toString()) // result is 1312
var d = 13;
m(d) // Combile Error
var e = "13";
m(3) // result is 1312
Additionally you can use JavaScript runtime type checking along with Typescript type guards.
function(a: string | number) {
If (typeof a == 'number') {
console.log('13' + a.toString());
} else {
console.log('13' + a);
}
}
Upvotes: -1
Reputation: 31924
Unfortunately for these cases, an emphasis is put on efficiency when compiling TypeScript to Javascript. That is, code that is not certainly necessary -- for example, the __extend
or __decorate
functions -- is omitted from the output. While lying to the compiler is not something that should be handled, runtime type errors can indeed sometimes arise from real use cases as well.
However, I would find this to be a weak reason for leaving all the advantages TypeScript provides, one of which happens to be an elegant solution to such runtime type mismatches: decorators. Especially when used in conjuction with reflect-metadata.
If you were to include m
as a method of a class, you could use a method decorator to ensure values of the correct types have been passed in at runtime:
function TypeCheck(
target: any,
propertyKey: string,
propertyDescriptor: PropertyDescriptor
): any {
var originalMethod = target[propertyKey].bind(target);
var paramTypes = Reflect.getMetadata("design:paramtypes", target, propertyKey);
// Return a new property descriptor for the decorated method.
return {
value: function () {
// Check argument types.
for (var i = 0; i < arguments.length; i++) {
switch (paramTypes[i]) {
case String:
if (!(arguments[i] instanceof paramTypes[i]) && typeof arguments[i] !== "string") {
throw new TypeError(`Expected argument ${i} to be of type 'string'.`);
}
break;
case Number:
if (!(arguments[i] instanceof paramTypes[i]) && typeof arguments[i] !== "number") {
throw new TypeError(`Expected argument ${i} to be of type 'number'.`);
}
break;
case Boolean:
if (!(arguments[i] instanceof paramTypes[i]) && typeof arguments[i] !== "boolean") {
throw new TypeError(`Expected argument ${i} to be of type 'boolean'.`);
}
break;
default:
if (!(arguments[i] instanceof paramTypes[i])) {
throw new TypeError(`Expected argument ${i} to be of type '${paramTypes[i].prototype.toString.call(paramTypes[i]).toString()}'.`);
}
break;
}
}
// Call original method.
return originalMethod.apply(target, arguments);
}
};
}
class Foo {
@TypeCheck
public m(a: string) {
console.log(a + 12);
}
}
var foo = new Foo();
foo.m(13 as any); // Will throw TypeError.
IMO, this offers the most elegant solution at the cost of some additional one-time setup. Yes, the TypeCheck
decorator looks bloated, but some types just require additional checking, because:
"some string" instanceof String; // false
... and reflect metadata will provide wrapper objects as type information for primitives as well.
Upvotes: 1