Skip to content

Drive by issue #7

@krispya

Description

@krispya

I ran into this issue while using the plugin with Koota but I don't have time to investigate it further right now. Here is the summary provided by Mr. Claude.


The issue is in the plugin's transformation of non-@inline const arrow functions that contain @inline calls in branch conditions with early returns.

Here's the problematic pattern:

export /* @inline @pure */ function isRelationPair(value: unknown): value is RelationPair {
    return (value as any)?.[$relationPair] as unknown as boolean;
}

export /* @inline @pure */ function isRelation(value: unknown): value is Relation {
    return (value as any)?.[$relation] as unknown as boolean;
}

// NOT annotated with @inline — const arrow inside a closure
const normalizeHookSubscription = (input: HookInput, callback: HookCallback) => {
    if (isRelationPair(input)) {          // <-- @inline call as if-condition
        const relation = input[$internal].relation;
        const pairTarget = input[$internal].target;
        const trait = relation[$internal].trait;

        if (pairTarget === '*') return { trait, callback };
        return {
            trait,
            callback: (entity: Entity, target?: Entity) => {
                if (target === pairTarget) callback(entity, target);
            },
        };
    }

    return {
        trait: isRelation(input) ? input[$internal].trait : input,  // <-- @inline call in ternary
        callback,
    };
};

After compilation, the entire isRelationPair branch was dropped. The call sites of normalizeHookSubscription compiled down to just:

// Only the final return path survived — the if(isRelationPair) branch is gone
const target = trait2?.[$relation] ? trait2[$internal].trait : trait2;
// callback is used raw, never wrapped
data.addSubscriptions.add(callback);

The function itself was also eliminated from the output (no normalizeHookSubscription identifier in the compiled chunk at all).

The fix was converting from a const arrow to two function declarations:

function resolveHookTrait(input: HookInput): Trait {
    if (isRelationPair(input)) return input[$internal].relation[$internal].trait;
    if (isRelation(input)) return input[$internal].trait;
    return input;
}

function resolveHookCallback(input: HookInput, callback: HookCallback): HookCallback {
    if (isRelationPair(input)) {
        const pairTarget = input[$internal].target;
        if (pairTarget === '*') return callback;
        return (entity: Entity, target?: Entity) => {
            if (target === pairTarget) callback(entity, target);
        };
    }
    return callback;
}

These appear correctly as resolveHookTrait ★ and resolveHookCallback ★ in the "Transformed functions" log and produce correct output.

So the bug seems to be: when a const arrow function (not @inline-annotated) has @inline function calls as if-conditions with early returns returning object literals, the transformation pass corrupts it — dropping all early-return branches and keeping only the final return path. function declarations with the same pattern work fine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions