Stmated
Stmated

Reputation: 495

Dynamic type inferring, or custom VariableScope. How is it done correctly?

I have a scripting system where depending on where the script is executed you have access to different variables. I also want to have inferred types for a type of Auto-Completion for the script editor.

But when the types are inferred during the compile phase, I have no way of giving a Binding which explains to the compilation phase what types those dynamic variables have.

I have currently solved this by:

Not compiling the code with either @TypeChecked nor @CompileStatic but later manually running a subclassed StaticCompilationVisitor on the dynamically typed codebase and manually filling in the StaticTypesMarker.INFERRED_TYPE inside visitVariableExpression() for the dynamic variables that I know exists.

However, this seems like the wrong way to go about it, and I would actually like to work with the VariableScope instead. But it seems to be under rough lockdown inside the VariableScopeVisitor, so it's difficult to pop in a CustomVariableScope that dynamically does the lookups. I have managed to do this with reflection, replacing the VariableScopeVisitor inside CompilationUnit and currentScope and such inside VaribleScopeVisitor. It works, but I don't like working against hard-coded private field names.

This might be a long-winded way of asking: Is there an official way of handling a situation of static typing with dynamic variables? I cannot do this by setting scriptBaseClass for reasons too complex to explain here.

If the question is unclear, please tell me and I'll try to edit in better explanations.

Upvotes: 0

Views: 104

Answers (1)

Stmated
Stmated

Reputation: 495

The answer was to add a GroovyTypeCheckingExtensionSupport to a StaticTypeCheckingVisitor and then use visitClass on the first ClassNode of the CompilationUnit.

final ClassNode classNode = this.compilationUnit.getFirstClassNode();
final StaticCompilationVisitor visitor = new StaticCompilationVisitor(this.sourceUnit, classNode);
visitor.addTypeCheckingExtension(new MyGroovyTypeCheckingExtensionSupport(visitor, this.compilationUnit));
visitor.visitClass(classNode);
visitor.performSecondPass();

And create something like the class below:

private static class MyGroovyTypeCheckingExtensionSupport extends GroovyTypeCheckingExtensionSupport {

    private static final ClassNode CLASSNODE_OBJECT = ClassHelper.make(Object.class);

    public MyGroovyTypeCheckingExtensionSupport(StaticTypeCheckingVisitor typeCheckingVisitor, CompilationUnit compilationUnit) {
        super(typeCheckingVisitor, "", compilationUnit);
    }

    @Override
    public boolean handleUnresolvedVariableExpression(VariableExpression vexp) {

        final ClassNode type = this.getType(vexp);
        if (type == null || type.equals(CLASSNODE_OBJECT)) {
            if (vextp.getName().equals("something")) {
                this.storeType(vexp, ClassHelper.make(SomeClass.class));
                return true;
            }
        }

        return false;
    }

}

Upvotes: 1

Related Questions