Support for a kind of #ifdebug / #endif

Topics: General, Language Specification
Jul 15, 2013 at 10:40 PM
Edited Jul 17, 2013 at 11:03 AM
Hello, I'm writing a small framework based on TypeScript, is there any plans to support the #ifdebug / #endif , this would help to debug.

Example of usage:
  1. tsc MyFileThatReferencesAllOtherTSFiles.ts -debug -out MyFramework.js
  2. tsc MyFileThatReferencesAllOtherTSFiles.ts -release -out MyFramework.js
In case of 1, the code that in the .ts files was between #ifdebug / #endif would make it into the JS, and it could contain code like "console.log( ... )".

In case of 2 it would cut the overhead between #ifdebug / #endif.

Thanks
Coordinator
Jul 17, 2013 at 7:09 PM
We don't have any plans at this time to do optional compilation of regions. Turns out that get that to work correctly, and work well, in an interactive editing environment is tricky to get right, so we chose not to focus on it for 1.0.

I'm not sure if anyone has created a fork of the compiler to add support just for the commandline experience, but it wouldn't surprise me as that's fairly straightforward.
Jul 19, 2013 at 5:26 AM
In languages like JavaScript or TypeScript, you don't need C-style conditional blocks.
// debug.js
function debug(d : ()=>any) { d(); }

// no-debug.js
function debug(d : ()=>any) { }
And now you just call them with:
debug(() => console.log("foo bar"));
And of course you can extend this to make easier to use log_debug functions or the like that are more convenient to the use in the common case.

You can do similar things for asserts or the like. You can use functions to ensure that side-effects are never called in release mode, or you can just take plain boolean arguments to make the syntax every more concise:
function assert(test:()=>bool, msg:()=>string) {
    if (!test())
        throw msg();
}

try
{
    assert(()=>0==1, ()=>"oh no");
}
catch (e)
{
    alert(e);
}
A more generic feature useful in other contexts might be D's lazy argument expression types, something like:
function assert(test: lazy bool, msg: lazy string) {
  if(!test)
    throw msg;
}

assert(some_expensive_test(), expensive_to_build_error());
Which in release mode, with an assert with an empty body, would compile down to a call to an empty function (which will be optimized away by most implementations).
Jul 19, 2013 at 1:06 PM
Which in release mode, with an assert with an empty body, would compile down to a call to an empty function (which will be optimized away by most implementations).
I assume you mean compiled away by the JIT. Do you have any references stating that? It seems unlikely to me, as functions (even empty ones) are first class objects in JavaScript that can have properties assigned to them arbitrarily. It would be unsafe for the JIT to ignore empty functions.

Either way, you're still instantiating anonymous functions, making method calls, and sending more code over the wire with this kind of a solution. It also forces you to structure code in a way that may not be the most natural. Finally, it requires you to use some 3rd party system besides the compiler to modify what is being built in different scenarios. I don't think this is an adequate substitute for conditional compilation.

There actually was a (very popular) feature request for conditional compilation that was closed a while back. It seems kind of strange to me to close a request for a popular feature which is pretty essential in the kind of application-scale JavaScript scenario TypeScript is meant to address (especially since the language is only at v0.9). I hope the team reconsiders and reopens it so the community at least has the ability to continue commenting on it.
Jul 20, 2013 at 2:52 AM
I assume you mean compiled away by the JIT. Do you have any references stating that?
You can inspect the output of compilers like V8 if you want to see.

Recall that almost every JS compiler today does some form of tracing; code paths running through empty functions repeatedly will get compiled down to a very slimline set of instructions, "bloated" over C/C++ mostly just by the include of type guards and the like (and a weaker set of optimizations).
It seems kind of strange to me to close a request for a popular feature which is pretty essential in the kind of application-scale JavaScript scenario TypeScript is meant to address
I'm not saying it's wrong, but that's a pretty strong claim to make.

I've seen pretty large apps in various languages not using or needing any such conditional debug statements; many C++ apps have them but run beautifully with them left compiled in (and most of the time, they should be).
Jul 21, 2013 at 12:14 PM
@SeanMiddleditch, with reference to your first post, what you have described is not really an alternative to a pre-processor directive.

How would the no-debug.js get referenced in a release build without actual manual intervention?

The thing that is being requested of TypeScript is the following for your code:
// debug.js (there is no no-debug.js)
function debug(d : ()=>any) { 

#if (DEBUG)
     d();
#endif

}

Certainly for small projects and hobbyists it's rather trivial to ensure one or the other file is included in a release build or to comment out a section or two of code. For larger projects this introduces yet another source of potential errors.

For example we currently have code that looks like this:
   // Development sources and keys        
   var foo = {
        root: "https://xxx.blob.core.foobar.net/",
        googlePlusKey: { id: "458588-dfdf1qdodfdfdfdfq7ok3i5d", key: "BIzdfGyCdfdfe8HpffgfRSb5_2Pdfdf" },
        facebookKey: { id: "123219585123132" }
     };

     // # BEGIN comment out prior to release
     foo.root = "https://yyy.blob.core.foobar.net/";
     foo.googlePlusKey = { id: "434343434", key: "AIzdfGyCdfdfe8HpffgfRSb5_2Pdfdf" };
     foo.facebookKey = { id: '4343434454545454545'};
     // # END comment
And a pre-release check list along the lines of
  1. Uncomment lines x to y in foo.ts.
  2. Comment out lines p to q in bar.ts
    ....
We'd dearly like to replace these with a simple #if (DEBUG) and not have to worry ever again.

I won't be surprised if someone were to pipe up saying you can use tool so-and-so to swap files in for a release build or run a macro that will do the job, but we don't want to maintain separate code files for release and debug or introduce yet another external tool. We'd like to see this solved organically within TypeScript, via the well established mechanism of a pre-processor directive.


Noel
Sep 13, 2013 at 5:25 PM
One more vote for a pre-processing facility in TypeScript that could conditionally strip out sections of code based on the project configuration (Debug vs. Release). It is a main aspect of using project configurations in other languages like C++. We would like to have debugging code - console trace, asserts - that are completely removed from Release builds.

It seems to me that the TypeScript compiler could easily add this in as an initial pass.
Mar 4, 2014 at 1:33 PM
You can already achieve this if you use UglifyJS as part of your build process. Uglify has a "global definitions" feature that lets you specify values of "constants" as part of the build.

I've created a Gist to illustrate how to do this: https://gist.github.com/markrendle/9347418
Mar 5, 2014 at 7:39 AM
@markrendle, that's not a bad solution for the most part. But obviously it's specific to UglifyJS.

Furthermore, I don't believe it's capable of handling cases such as the following:
#if(DEBUG)
       import foo = require('bar-debug');
#else
       import foo = require('bar-humbug');
#endif

function doFoo(){
    foo.someMethod();
}
or

#if(DEBUG)
       class Foo { doFoo(){ console.log('debug'); }  }
#else
       class Foo { doFoo(){ console.log('release'); }  }
#endif

var foo = new Foo();