Skip to content

[FEATURE] Add support for static locals - #561

Merged
AbnormalPoof merged 4 commits into
FunkinCrew:developfrom
ahmed7p:static-locals
Oct 18, 2026
Merged

AbnormalPoof merged 4 commits into
FunkinCrew:developfrom
ahmed7p:static-locals

Conversation

@ahmed7p

@ahmed7p ahmed7p commented Oct 16, 2026

Copy link
Copy Markdown

Linked Issues

#321

Description

This PR adds support for static local variables (static var / static final)

Key Changes:

  • Parser & Expr:
    • Extended EVar and EFinal expressions to accept an optional isStatic flag.
    • Added support for parsing static var and static final declarations.
    • Fixed variable declaration parsing and commas handling when using final a,b,c declarations.
    • Added EInvalidStaticLocalInit error type.
  • Interp:
    • Implemented unique storage mapping ($clsName#$funcName#$varName) in scriptInterp.variables so static locals retain their state across function calls and loop executions.
    • Updated function scoping (currentFunction, oldFunctionName) during EFunction execution to ensure accurate context tracking.
  • Validation & Safety Checks:
    • Added strict checks to prevent invalid initializers (e.g., blocking switch expressions, this, super, instance fields, and non-static local variables).
    • Added conflict validation against existing class static fields.
    • Added validateAssignment to block expressions that do not return values during variable assign.

Examples

Comprehensive Usage Examples:

function testStaticLocals() {
    // 1. Basic state persistence across function calls
    static var hitCount = 0;
    hitCount++; 
    trace('Hits: $hitCount'); // Persists and increments values across re-entries

    // 2. Using static final for read-only configuration/constants
    static final MAX_LIMIT = 100;
    trace('Max limit is: $MAX_LIMIT');

    // 3. Using compound assignments (+=, -=, etc.)
    static var score = 10;
    score += 5; // Properly updates and persists state in scriptInterp.variables
    trace('Score: $score');

    // 4. Multiple static declarations with comma separation
    static var a = 1, b = 2, c = 3;
    a += 10;
    trace('a: $a, b: $b, c:$c');
}

function getModSettings() {
    // Initialized and cached on first call
    static var cachedConfig:Map<String, Dynamic> = null;
    
    if (cachedConfig == null) {
        cachedConfig = CoolUtil.parseJson(Assets.getText('assets/data/global-mod-config.json'));
    }
    return cachedConfig;
}

function recordSessionStats(noteMissed:Bool) {
    // Persists across different songs in Story Mode or multiple retries
    static var totalSessionMisses = 0;
    
    if (noteMissed) {
        totalSessionMisses++;
        if (totalSessionMisses >= 10) {
            triggerSecretAchievement();
        }
    }
}

2. Error Handling (Invalid Initializer):

function staticLocalErrors(val:Int) {
    var normalLocal = 10;

    // 1. Error: Using a non-static local variable as an initializer
    // Throws: Cannot initialize static variable "staticLocalErrors_x": cannot use local variable "normalLocal" as an initializer
    static var x = normalLocal; 

    // 2. Error: Using "this" as an initializer
    // Throws: Cannot initialize static variable "staticLocalErrors_y": cannot use "this" as initialization
    static var y = this; 

    // 3. Error: Using "super" as an initializer
    // Throws: Cannot initialize static variable "staticLocalErrors_z": cannot use "this" as initialization
    static var z = super; 

    // 4. Error: Using an instance field as an initializer
    // Throws: Cannot initialize static variable "staticLocalErrors_w": cannot use instance field "someInstanceField" as an initializer
    static var w = someInstanceField; 

    // 5. Error: Using a switch expression as an initializer
    // Throws: Cannot initialize static variable "staticLocalErrors_v": cannot use a switch expression as an initializer
    static var v = switch (val) {
        case 1: "One";
        default: "Other";
    }; 

    // 6. Error: Using an expression that does not return a value (e.g., while loop)
    // Throws: Cannot assign an expression that does not return a value.
    var u = while (false) {}; 

    // 7. Error: Name conflict with an existing local static field
   static var existingStaticField = 100; 
    // Throws: The expanded name of static local "staticLocalErrors_existingStaticField" conflicts with another static field
    static var existingStaticField = "serious_car"; 
}

@nykwono

nykwono commented Oct 16, 2026

Copy link
Copy Markdown

good

@AbnormalPoof
AbnormalPoof merged commit 852db19 into FunkinCrew:develop Oct 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants