<commit_before>import ../frontend/[Token, BuildParams, AstBuilder], io/File
import BinaryOp, Visitor, Expression, VariableDecl, FunctionDecl,
       TypeDecl, Declaration, Type, Node, ClassDecl, NamespaceDecl,
       EnumDecl, PropertyDecl, FunctionCall, Module, Import, FuncType,
       NullLiteral, AddressOf, BaseType, StructLiteral, Return,
       Argument, Scope, CoverDecl, StringLiteral

import tinker/[Resolver, Response, Trail, Errors]
import structs/ArrayList

// for built-ins
import os/[System, Time], rock/RockVersion, rock/frontend/Target

VariableAccess: class extends Expression {

    _warned := false    
    _staticFunc : FunctionDecl = null
    
    expr: Expression {
        get
        set (newExpr) {
            expr = newExpr
            match newExpr {
                case acc: VariableAccess => acc reverseExpr = this
            }
        }
    }
    
    reverseExpr: VariableAccess
    
    name: String

    ref: Declaration

    init: func ~variableAccess (.name, .token) {
        init(null, name, token)
    }

    init: func ~variableAccessWithExpr (=expr, =name, .token) {
        super(token)
    }

    init: func ~varDecl (varDecl: VariableDecl, .token) {
        super(token)
        name = varDecl getName()
        ref = varDecl
    }

    clone: func -> This {
        new(expr ? expr clone() : null, name, token)
    }

    init: func ~typeAccess (type: Type, .token) {
        super(token)
        name = type getName()
        if(type getRef() instanceOf?(VariableDecl)) {
            varDecl := type getRef() as VariableDecl
            if(varDecl getOwner() != null) {
                if(varDecl isStatic) {
                    expr = VariableAccess new(varDecl getOwner() getInstanceType(), token)
                } else {
                    expr = VariableAccess new("this", token)
                }
            }
        } else {
            // else, it's safe to carry the ref
            ref = type getRef()
        }
    }

    accept: func (visitor: Visitor) {
        visitor visitVariableAccess(this)
    }

    // It's just an access, it has no side-effects whatsoever
    hasSideEffects : func -> Bool { false }

    debugCondition: inline func -> Bool {
        false
    }

    suggest: func (node: Node) -> Bool {
        match node {
            case candidate: VariableDecl =>
                // if we're accessing a member, we're expecting the
                // candidate to belong to a TypeDecl..
                if(isMember() && !candidate isMember()) {
                    return false
                }

                if(_staticFunc && candidate isMember() && candidate == candidate owner thisDecl) {
                    //token formatMessage("Got thisDecl of " + candidate owner toString() + " in static func " + _staticFunc toString(), "INFO") println()

                    // Can't access an instance variable from static function
                    return false
                }

                ref = candidate
                if(isMember() && candidate owner isMeta) {
                    expr = VariableAccess new(candidate owner getNonMeta() getInstanceType(), candidate token)
                }

                true
            case candidate: FunctionDecl =>
                // if we're accessing a member, we're expecting the candidate
                // to belong to a TypeDecl..
                if((expr != null) && (candidate owner == null)) {
                    return false
                }

                ref = candidate
                true
            case tDecl: TypeDecl =>
                // TODO: the use of 'val' here is a workaround - an if/else should
                // be an expression.
                val := true
                if(tDecl isAddon()) {
                    // First rule of resolve club is: you do not resolve to an addon.
                    // Always resolve to the base instead.
                    val = suggest(tDecl getBase() getNonMeta())
                } else {
                    ref = node
                }
                val
            case nDecl: NamespaceDecl =>
                ref = node
                true
            case =>
                false
        }
    }

    isResolved: func -> Bool { ref != null && getType() != null }

    resolve: func (trail: Trail, res: Resolver) -> Response {

        if(debugCondition()) {
            "%s is of type %s" printfln(name, getType() ? getType() toString() : "(nil)")
        }

        // resolve built-ins first
        builtin := getBuiltin(name)
        if (builtin) {
            if(!trail peek() replace(this, builtin)) {
                res throwError(CouldntReplace new(token, this, builtin, trail))
            }
            res wholeAgain(this, "builtin replaced")
            return Response OK
        }

        trail onOuter(FunctionDecl, |fDecl|
            if(fDecl isStatic()) _staticFunc = fDecl
        )

        if(expr) {
            trail push(this)
            response := expr resolve(trail, res)
            trail pop(this)
            if(!response ok()) return response
        }

        if(expr && name == "class") {
            if(expr getType() == null || expr getType() getRef() == null) {
                res wholeAgain(this, "expr type or expr type ref is null")
                return Response OK
            }
            if(!expr getType() getRef() instanceOf?(ClassDecl)) {
                name = expr getType() getName()
                ref = expr getType() getRef()
                expr = null
            }
        }

        /*
         * Try to resolve the access from the expr
         */
        if(!ref && expr) {
            if(expr instanceOf?(VariableAccess) && expr as VariableAccess getRef() != null \
              && expr as VariableAccess getRef() instanceOf?(NamespaceDecl)) {
                expr as VariableAccess getRef() resolveAccess(this, res, trail)
            } else {
                exprType := expr getType()
                if(exprType == null) {
                    res wholeAgain(this, "expr's type isn't resolved yet, and it's needed to resolve the access")
                    return Response OK
                }
                typeDecl := exprType getRef()
                if(!typeDecl) {
                    if(res fatal) res throwError(UnresolvedType new(expr token, expr getType(), "Can't resolve type %s" format(expr getType() toString())))
                    res wholeAgain(this, "unresolved access, looping")
                    return Response OK
                }
                typeDecl resolveAccess(this, res, trail)

                //If we did'nt get the ref, we try to get it from the cover's "from type"
                if(!ref && typeDecl instanceOf?(CoverDecl)) {
                    typeDecl as CoverDecl resolveAccessInFromType(this, res, trail)
                }
            }
        }

        /*
         * Try to resolve the access from the trail
         *
         * It's far simpler than resolving a function call, we just
         * explore the trail from top to bottom and retain the first match.
         */
        if(!ref && !expr) {<commit_msg>Disallow variable access in expr of pointer type without dereferencing it first<commit_after>import ../frontend/[Token, BuildParams, AstBuilder], io/File
import BinaryOp, Visitor, Expression, VariableDecl, FunctionDecl,
       TypeDecl, Declaration, Type, Node, ClassDecl, NamespaceDecl,
       EnumDecl, PropertyDecl, FunctionCall, Module, Import, FuncType,
       NullLiteral, AddressOf, BaseType, StructLiteral, Return,
       Argument, Scope, CoverDecl, StringLiteral

import tinker/[Resolver, Response, Trail, Errors]
import structs/ArrayList

// for built-ins
import os/[System, Time], rock/RockVersion, rock/frontend/Target

VariableAccess: class extends Expression {

    _warned := false    
    _staticFunc : FunctionDecl = null
    
    expr: Expression {
        get
        set (newExpr) {
            expr = newExpr
            match newExpr {
                case acc: VariableAccess => acc reverseExpr = this
            }
        }
    }
    
    reverseExpr: VariableAccess
    
    name: String

    ref: Declaration

    init: func ~variableAccess (.name, .token) {
        init(null, name, token)
    }

    init: func ~variableAccessWithExpr (=expr, =name, .token) {
        super(token)
    }

    init: func ~varDecl (varDecl: VariableDecl, .token) {
        super(token)
        name = varDecl getName()
        ref = varDecl
    }

    clone: func -> This {
        new(expr ? expr clone() : null, name, token)
    }

    init: func ~typeAccess (type: Type, .token) {
        super(token)
        name = type getName()
        if(type getRef() instanceOf?(VariableDecl)) {
            varDecl := type getRef() as VariableDecl
            if(varDecl getOwner() != null) {
                if(varDecl isStatic) {
                    expr = VariableAccess new(varDecl getOwner() getInstanceType(), token)
                } else {
                    expr = VariableAccess new("this", token)
                }
            }
        } else {
            // else, it's safe to carry the ref
            ref = type getRef()
        }
    }

    accept: func (visitor: Visitor) {
        visitor visitVariableAccess(this)
    }

    // It's just an access, it has no side-effects whatsoever
    hasSideEffects : func -> Bool { false }

    debugCondition: inline func -> Bool {
        false
    }

    suggest: func (node: Node) -> Bool {
        match node {
            case candidate: VariableDecl =>
                // if we're accessing a member, we're expecting the
                // candidate to belong to a TypeDecl..
                if(isMember() && !candidate isMember()) {
                    return false
                }

                if(_staticFunc && candidate isMember() && candidate == candidate owner thisDecl) {
                    //token formatMessage("Got thisDecl of " + candidate owner toString() + " in static func " + _staticFunc toString(), "INFO") println()

                    // Can't access an instance variable from static function
                    return false
                }

                ref = candidate
                if(isMember() && candidate owner isMeta) {
                    expr = VariableAccess new(candidate owner getNonMeta() getInstanceType(), candidate token)
                }

                true
            case candidate: FunctionDecl =>
                // if we're accessing a member, we're expecting the candidate
                // to belong to a TypeDecl..
                if((expr != null) && (candidate owner == null)) {
                    return false
                }

                ref = candidate
                true
            case tDecl: TypeDecl =>
                // TODO: the use of 'val' here is a workaround - an if/else should
                // be an expression.
                val := true
                if(tDecl isAddon()) {
                    // First rule of resolve club is: you do not resolve to an addon.
                    // Always resolve to the base instead.
                    val = suggest(tDecl getBase() getNonMeta())
                } else {
                    ref = node
                }
                val
            case nDecl: NamespaceDecl =>
                ref = node
                true
            case =>
                false
        }
    }

    isResolved: func -> Bool { ref != null && getType() != null }

    resolve: func (trail: Trail, res: Resolver) -> Response {

        if(debugCondition()) {
            "%s is of type %s" printfln(name, getType() ? getType() toString() : "(nil)")
        }

        // resolve built-ins first
        builtin := getBuiltin(name)
        if (builtin) {
            if(!trail peek() replace(this, builtin)) {
                res throwError(CouldntReplace new(token, this, builtin, trail))
            }
            res wholeAgain(this, "builtin replaced")
            return Response OK
        }

        trail onOuter(FunctionDecl, |fDecl|
            if(fDecl isStatic()) _staticFunc = fDecl
        )

        if(expr) {
            trail push(this)
            response := expr resolve(trail, res)
            trail pop(this)
            if(!response ok()) return response
        }

        if(expr && name == "class") {
            if(expr getType() == null || expr getType() getRef() == null) {
                res wholeAgain(this, "expr type or expr type ref is null")
                return Response OK
            }
            if(!expr getType() getRef() instanceOf?(ClassDecl)) {
                name = expr getType() getName()
                ref = expr getType() getRef()
                expr = null
            }
        }

        /*
         * Try to resolve the access from the expr
         */
        if(!ref && expr) {
            if(expr instanceOf?(VariableAccess) && expr as VariableAccess getRef() != null \
              && expr as VariableAccess getRef() instanceOf?(NamespaceDecl)) {
                expr as VariableAccess getRef() resolveAccess(this, res, trail)
            } else {
                exprType := expr getType()
                if(exprType == null) {
                    res wholeAgain(this, "expr's type isn't resolved yet, and it's needed to resolve the access")
                    return Response OK
                }
                typeDecl := exprType getRef()
                if(!typeDecl) {
                    if(res fatal) res throwError(UnresolvedType new(expr token, expr getType(), "Can't resolve type %s" format(expr getType() toString())))
                    res wholeAgain(this, "unresolved access, looping")
                    return Response OK
                }

                typeDecl resolveAccess(this, res, trail)
                if(exprType instanceOf?(PointerType)) {
                    res throwError(InvalidAccess new(this, "Can't access field '%s' in expression of pointer type '%s' without dereferencing it first" \
                                                           format(name, exprType toString())))
                    return Response OK
                }

                //If we did'nt get the ref, we try to get it from the cover's "from type"
                if(!ref && typeDecl instanceOf?(CoverDecl)) {
                    typeDecl as CoverDecl resolveAccessInFromType(this, res, trail)
                }
            }
        }

        /*
         * Try to resolve the access from the trail
         *
         * It's far simpler than resolving a function call, we just
         * explore the trail from top to bottom and retain the first match.
         */
        if(!ref && !expr) {
{'fraction_non_alphanumeric': 0.09374151506923703, 'fraction_numerical': 0.0, 'mean_word_length': 1.5120204603580563, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6423100', 'n_tokens_mistral': 3826, 'n_tokens_neox': 3561, 'n_words': 1208}