Debugging and determining the runtime class of something

Hello,
I am trying to write an interpreter and need to detect the runtime class of objects.
I have different nodes in the abstract syntax tree and some are addition of numbers, some are booleans some are strings. The first method is correct in that in returns a Double object. But its return type is cast to Expression.ExpressionLiteral

Does anyone know how I can specify that even though the return type is Any the information from subtypes of Any like Double will not be lost through typecasting to Any?

override fun visitUnaryExpression(obj: Expression.UnaryExpression): Any {
        val right = evaluate(obj.right)
        println(right.javaClass) // class.Double
        println(right is Expression.LiteralExpression) //false
        println(right.toString()) //1.0


data class UnaryExpression(val operator:Token,val right:Expression):Expression(){
override fun accept(visitor:Visitor):Any { 
// but here the return value is Expression.UnaryExpression
return visitor.visitUnaryExpression(this) 
    }
} 

I’m not sure I fully understood your question, but I think the answer is that you can’t. If the return type of a function is Any, then at compile time, the only things you can know about it are that it’s an instance of Any. You can use is checks to work out what the type is, or do a type cast if you know what it is, but you can’t have a function that returns Any, and then elsewhere in your code magically treat that return type as a Double. You need to either check that it’s a Double, or type cast it to a Double.

What you might want to look into is using Generics; that might allow you to have the precision you want, but at compile time.

How would you model that with generics? So I have a node

data class BinaryExpression(val left:Expression,val operator:Token,val right:Expression):Expression(){
override fun accept(visitor:Visitor):Any { return visitor.visitBinaryExpression(this) }

and it can hold a double, an int, a string, a boolean. How would you model this with generics?

Keep in mind, I only said that generics might allow you to have the precision you want. You’ve only shown a small amount of your code, so I can’t promise anything.

I’m not sure whether it’s your BinaryExpression class that holds the value, or the Visitor? Either way, you could include a type parameter on the class.

Assuming that BinaryExpression holds the value, and Token is the value, you could model it like this:

data class BinaryExpression<T>(
    val left: Expression,
    val operator: Token<T>,
    val right: Expression
) : Expression() {
    override fun accept(visitor: Visitor): T {
        return visitor.visitBinaryExpression(this)
    }
}

Or, if Visitor has the value, like this:

data class BinaryExpression(
    val left: Expression,
    val operator: Token,
    val right: Expression
) : Expression() {
    override fun <T> accept(visitor: Visitor<T>): T {
        return visitor.visitBinaryExpression(this)
    }
}