I have this ts.Program/ts.SourceFile:
class MyClass {
foo() {
// #1
}
}
Is there a way to obtain the symbol/type of the local this value at position #1?
Unfortunately, this is not listed by checker.getSymbolsInScope.
Ideally I would get the type of MyClass, so I could explore its members.
The solution should also work for these cases:
function foo(this: MyClass) {
// #2
}
const obj = {
bar() {
// #3
}
};
Thank you!
edit: This is the feature I needed this for:
There is an internal TypeChecker#tryGetThisTypeAt(nodeIn: ts.Node, includeGlobalThis = true): ts.Type method that will return this information. If you're ok with using internal API, then you can do:
const type = (checker as any).tryGetThisTypeAt(nodeName) as ts.Type;
To get the node's name, you'll need to traverse the tree to find the method or function declaration that is contained within the position, then the .name property will contain the node that you can provide to tryGetThisTypeAt.
I want to print values of properties of my class.
fun print() {
val cl = this::class
cl.declaredMemberProperties.filter {it.visibility != KVisibility.PRIVATE}.forEach {
println("${it.name} = ${it.get(this)}")
}
}
When I try to build this code I get compiler error:
Error:(34, 40) Kotlin: Out-projected type 'KProperty1<out SomeClass, Any?>' prohibits the use of 'public abstract fun get(receiver: T): R defined in kotlin.reflect.KProperty1'
When I change this to class name SomeClass everything is fine
fun print() {
val cl = SomeClass::class
cl.declaredMemberProperties.filter {it.visibility != KVisibility.PRIVATE}.forEach {
println("${it.name} = ${it.get(this)}")
}
}
So the problem is that compiler changers type of this::class to KClass<out SomeClass> instead of using KClass<SomeClass>. Any idea why does it happen?
The reason for this difference is that, when you use the SomeClass::class reference, it is sure to be the class token representing SomeClass and not one of its possible derived classes, therefore it is KClass<SomeClass> without type projections.
But this::class written in a function of an open or abstract class or an extension function can return a class token of a derived class, therefore, to ensure type safety, the type is out-projected: KClass<out SomeClass> means that the actual type argument can be SomeClass or its subtype.
Example:
open class A {
fun f() {
println(this::class) // KClass<out A> because it can be KClass<B>
}
}
class B : A()
B().f()
I'm not sure of the best approach for handling scoping of "this" in TypeScript.
Here's an example of a common pattern in the code I am converting over to TypeScript:
class DemonstrateScopingProblems {
private status = "blah";
public run() {
alert(this.status);
}
}
var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run();
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run);
Now, I could change the call to...
$(document).ready(thisTest.run.bind(thisTest));
...which does work. But it's kinda horrible. It means that code can all compile and work fine in some circumstances, but if we forget to bind the scope it will break.
I would like a way to do it within the class, so that when using the class we don't need to worry about what "this" is scoped to.
Any suggestions?
Update
Another approach that works is using the fat arrow:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Is that a valid approach?
You have a few options here, each with its own trade-offs. Unfortunately there is no obvious best solution and it will really depend on the application.
Automatic Class Binding
As shown in your question:
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
alert(this.status);
}
}
Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the this context instead of each call site creating a new closure upon invoke.
Good: Impossible for external callers to forget to handle this context
Good: Typesafe in TypeScript
Good: No extra work if the function has parameters
Bad: Derived classes can't call base class methods written this way using super.
Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
Function.bind
Also as shown:
$(document).ready(thisTest.run.bind(thisTest));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: No extra work if the function has parameters
Bad: In TypeScript, this currently has no type safety
Bad: Only available in ECMAScript 5, if that matters to you
Bad: You have to type the instance name twice
Fat arrow
In TypeScript (shown here with some dummy parameters for explanatory reasons):
$(document).ready((n, m) => thisTest.run(n, m));
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: In TypeScript, this has 100% type safety
Good: Works in ECMAScript 3
Good: You only have to type the instance name once
Bad: You'll have to type the parameters twice
Bad: Doesn't work with variadic parameters
Another solution that requires some initial setup but pays off with its invincibly light, literally one-word syntax is using Method Decorators to JIT-bind methods through getters.
I've created a repo on GitHub to showcase an implementation of this idea (it's a bit lengthy to fit into an answer with its 40 lines of code, including comments), that you would use as simply as:
class DemonstrateScopingProblems {
private status = "blah";
#bound public run() {
alert(this.status);
}
}
I haven't seen this mentioned anywhere yet, but it works flawlessly. Also, there is no notable downside to this approach: the implementation of this decorator -- including some type-checking for runtime type-safety -- is trivial and straightforward, and comes with essentially zero overhead after the initial method call.
The essential part is defining the following getter on the class prototype, which is executed immediately before the first call:
get: function () {
// Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
// instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
var instance = this;
Object.defineProperty(instance, propKey.toString(), {
value: function () {
// This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
return originalMethod.apply(instance, arguments);
}
});
// The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
// JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
return instance[propKey];
}
Full source
The idea can be also taken one step further, by doing this in a class decorator instead, iterating over methods and defining the above property descriptor for each of them in one pass.
Necromancing.
There's an obvious simple solution that doesn't require arrow-functions (arrow-functions are 30% slower), or JIT-methods through getters.
That solution is to bind the this-context in the constructor.
class DemonstrateScopingProblems
{
constructor()
{
this.run = this.run.bind(this);
}
private status = "blah";
public run() {
alert(this.status);
}
}
You can write an autobind method to automatically bind all functions in the constructor of the class:
class DemonstrateScopingProblems
{
constructor()
{
this.autoBind(this);
}
[...]
}
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
const val = self[key];
if (key !== 'constructor' && typeof val === 'function')
{
// console.log(key);
self[key] = val.bind(self);
} // End if (key !== 'constructor' && typeof val === 'function')
} // Next key
return self;
} // End Function autoBind
Note that if you don't put the autobind-function into the same class as a member function, it's just autoBind(this); and not this.autoBind(this);
And also, the above autoBind function is dumbed down, to show the principle.
If you want this to work reliably, you need to test if the function is a getter/setter of a property as well, because otherwise - boom - if your class contains properties, that is.
Like this:
export function autoBind(self)
{
for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
{
if (key !== 'constructor')
{
// console.log(key);
let desc = Object.getOwnPropertyDescriptor(self.constructor.prototype, key);
if (desc != null)
{
if (!desc.configurable) {
console.log("AUTOBIND-WARNING: Property \"" + key + "\" not configurable ! (" + self.constructor.name + ")");
continue;
}
let g = desc.get != null;
let s = desc.set != null;
if (g || s)
{
var newGetter = null;
var newSetter = null;
if (g)
newGetter = desc.get.bind(self);
if (s)
newSetter = desc.set.bind(self);
if (newGetter != null && newSetter == null) {
Object.defineProperty(self, key, {
get: newGetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else if (newSetter != null && newGetter == null) {
Object.defineProperty(self, key, {
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
else {
Object.defineProperty(self, key, {
get: newGetter,
set: newSetter,
enumerable: desc.enumerable,
configurable: desc.configurable
});
}
continue; // if it's a property, it can't be a function
} // End if (g || s)
} // End if (desc != null)
if (typeof (self[key]) === 'function')
{
let val = self[key];
self[key] = val.bind(self);
} // End if (typeof (self[key]) === 'function')
} // End if (key !== 'constructor')
} // Next key
return self;
} // End Function autoBind
In your code, have you tried just changing the last line as follows?
$(document).ready(() => thisTest.run());
I have a variable like this:
People model=Mock()//created by spock' mock
How to change the variable name "model" to "model_0" by reflection?
I doubt there is any way to do that with reflection. I'm with #tim_yates in this one.
I'm no bytecode/compiler expert, but i do believe variable's names turn into symbols upon compilation, so they are not changeable at all. The following Java class:
public class Var {
void a() {
int myVar = 1;
myVar += 1;
}
}
Upon compilation and decompilation (using jd-gui), the code turns into:
public class Var
{
void a()
{
int i = 1;
i++;
}
}
The variable name changed.
On Groovy, you can go for ASTs, which will give you full power over the generated tree. The following class:
class Asts {
def wat() {
Integer myVar = 90
}
}
Will generate the following AST:
Now you can write your own AST transformation to make the changes. Doesn't seem worth to me, though, a collection or map should suffice.
I got two classes, one deriving from the other, and on the base class I need to check if the derived class is implementing a method with a specific name:
class Foo {
constructor() { }
childHasMethod() {
if(this.method) {
console.log('Yay');
} else {
console.log('Nay');
}
}
}
class Bar extends Foo {
constructor() {
super();
this.childHasMethod();
}
method() {
}
}
var bar = new Bar();
Even though the line if(this.method) { is marked red on the playground, it works. But the local compiler throws a compilation error: The property 'method' does not exist on value of type 'Foo'.
Is there a clean way to achieve what I'm trying to do?
In order to "sneak it past the compiler" you can treat this as dynamic:
(<any>this).method
I have made a full example of this on the TypeScript Playground.
childHasMethod() {
if((<any>this).method) {
alert('Yay');
} else {
alert('Nay');
}
}
Having said this, having a base class know details about its sub-classes could get you into tricky places. Usually I would try to avoid this as it sounds like the specialisations are leaking into the base class - but you may have a particular thing you are doing and know your program better than me so I'm not saying "don't do this" - just "are you sure" :)