RFC-0024: Remove @@import — host-language imports via Oceans Model

Summary

Remove the @@import directive from the Frame language. Cross-file dependencies are expressed entirely in the target host language’s native syntax — from .counter import Counter for Python, use crate::counter::Counter; for Rust, const Counter = preload("res://counter.gd") for GDScript, and so on — written by the user as Oceans Model pass-through. framec emits state-machine classes; the host language’s import system resolves the names.

Motivation

RFC-0022 introduced @@import for three reasons:

  1. Auto-emit native imports on the fourteen file-path-resolving backends.
  2. Validate cross-file references at framec-compile time under --import-mode strict.
  3. Resolve @@SystemName() references in handler bodies — give framec confidence that a name like @@Counter() corresponds to a real @@system declaration somewhere in the dependency set.

RFC-0022.1 reduced (1) on three backends (Java, C#, Go) because their native import vocabulary requires information framec doesn’t carry (host packaging). Users on those backends already write native imports via Oceans Model pass-through.

Walking through the remaining responsibilities of @@import:

  • (1) Auto-emit native imports. Convenience: saves the user typing one native line per imported file. Users already know how to write from .other import X in their target language; framec adds no value by translating from a Frame-specific spelling.
  • (2) Validation. Duplicates work the host compiler already does. javac, rustc, the Python runtime — every host catches missing imports. Strict mode just shifts the error one pipeline-step earlier.
  • (3) Resolve @@SystemName(). This was the load-bearing reason. But on close inspection it isn’t: framec lowers @@Counter() using the literal name from source. The factory call shape is target-uniform per backend — Counter._create(), Counter::new(), Counter.new(), new Counter() — and only needs the identifier “Counter.” Whether Counter is defined in the same file or imported from another is irrelevant to codegen. The host language’s import system resolves the name at host-compile time; if the user forgot to import, the host compiler errors.

Three responsibilities, three honest answers: convenience, duplicate work, target-uniform lowering that doesn’t need the directive at all. None survive scrutiny.

This RFC removes @@import and --import-mode entirely. Frame becomes simpler; the language surface shrinks; cross-file composition becomes whatever the host language already does.

This is the same trajectory taken for Java’s runtime imports (RFC-0022.1 + the FQ-types migration): when the host language has a working mechanism, framec gets out of the way.

The contract

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY are to be interpreted as in RFC 2119.

Syntax

The @@import directive is removed from the language. A Frame source file containing @@import at module scope is a parse error (E823). The compiler SHOULD include a migration pointer in the error message: “Replace with the target language’s native import syntax outside any @@system block. See RFC-0024.”

The --import-mode CLI flag is removed. The strict-mode validators E821 (unreadable import) and E822 (no system in imported file) are removed.

Cross-file @@SystemName() lowering

@@SystemName(args) in a handler body or domain initializer lowers to the target’s factory call pattern using only the literal name SystemName from the source. framec MUST NOT verify that SystemName corresponds to a declaration anywhere in the project. If the host language’s import system fails to resolve the name at host-compile time, that’s the host’s error to report.

This is unchanged from current behavior for in-file @@SystemName() — framec already lowers using just the name. The RFC formalizes that the lowering doesn’t require declaration knowledge.

User responsibility

The user writes native imports in their target language’s idiom, outside any @@system block. framec emits them verbatim via Oceans Model pass-through.

Examples

Python — two-file composition

counter.fpy:

@@[target("python_3")]

@@system Counter {
    interface:
        bump()
        get(): int
    machine:
        $Active {
            bump()      { self.n = self.n + 1 }
            get(): int  { @@:(self.n) }
        }
    domain:
        n: int = 0
}

app.fpy:

@@[target("python_3")]

from .counter import Counter

@@system App {
    interface:
        run()
    machine:
        $Active {
            run() { self.c.bump() }
        }
    domain:
        c = @@Counter()
}

Generated app.py:

from .counter import Counter

class App:
    # ...
    @classmethod
    def _create(cls):
        c = cls()
        c.c = Counter._create()
        # ...

@@Counter() lowers to Counter._create() — framec uses the literal name. The user’s from .counter import Counter line lands verbatim via Oceans Model.

Rust — same shape

app.frs:

@@[target("rust")]

use crate::counter::Counter;

@@system App {
    domain:
        c: Counter = @@Counter()
    // ...
}

Generated app.rs carries the use line and emits Counter::new() at the call site.

Java — same shape (post RFC-0022.1, now without @@import)

app.fjava:

@@[target("java")]

package com.example.app;

import com.example.counter.Counter;

@@system App {
    domain:
        c: Counter = @@Counter()
    // ...
}

This was already the required style on Java/C#/Go per RFC-0022.1; this RFC removes the optional @@import declaration that those backends never honored anyway.

GDScript — preload

app.fgd:

@@[target("gdscript")]

const Counter = preload("res://counter.gd")

@@system App {
    domain:
        c = @@Counter()
}

framec emits Counter.new() at the call site; the preload binding from the user prolog makes Counter resolvable.

Alternatives

A. Status quo (RFC-0022 + RFC-0022.1)

Keep @@import with current semantics: native-emit on 14 backends, dependency-declaration-only on Java/C#/Go.

Rejected. Three reasons. (1) The directive adds language surface without doing work the host can’t already do. (2) The 14-vs-3 split is a smell — different backends behave differently on the same syntax, surfacing as user confusion. (3) The trajectory across recent RFCs (RFC-0022.1, Java FQ types, Oceans Model glossary) is “framec stops translating things the host language already handles”; keeping @@import is the inconsistent exception.

B. Soft deprecation (warning, hard-cut later)

Emit a deprecation warning W707 when @@import is used; remove the directive in a later release.

Rejected for pre-1.0. Frame is pre-1.0; hard cuts are the established convention (RFC-0013 hard-cut @@persist and @@target; RFC-0015 hard-cut the @@: operation-attribute form). Soft-deprecating delays the cleanup without changing the eventual outcome.

C. Keep @@import as pure validation, no codegen on any backend

Make @@import a “dependency-declaration only” on every backend (extending RFC-0022.1’s Java/C#/Go behavior universally). Removes the 14-vs-3 split but keeps the directive.

Rejected. This is just RFC-0022.1 generalized. It preserves the validation-time error (one step earlier than host-compile) but costs language surface. The host compiler already catches missing imports; the early-detection benefit is small. Removing entirely is cleaner.

Migration

Pre-1.0 hard cut. After framec 4.2.0 (or whatever version ships this RFC), @@import parses to E823.

Codemod for the 14 file-path backends. A mechanical sweep replaces each @@import "./other.f<ext>" line with the target’s native import:

Target Replacement
Python from .<filename> import <Systems...>
GDScript const <System> = preload("res://<filename>.gd") per system
Rust use crate::<filename>::{<Systems...>};
JS / TS import { <Systems...> } from "./<filename>";
Dart import '<filename>.dart';
C / C++ #include "<filename>.h"
PHP require_once __DIR__ . '/<filename>.php';
Ruby require_relative '<filename>'
Lua local <Cap> = require '<filename>'
Erlang (no replacement — single OTP application)
Kotlin import com.example.<filename>.<Systems...> (package conv.)
Swift (same module; typically no import needed)

The list of <Systems...> is peek-able by reading the imported file’s @@system declarations — the same peek RFC-0022 already performs. A codemod ships alongside the RFC release.

Java / C# / Go fixtures need no migration. They already use native imports via Oceans Model (per RFC-0022.1); the @@import line on those files was already a no-op and is just deleted.

Documentation. Per-language guides remove the “RFC-0022 @@import works on this backend” sections; cross-file composition guidance moves entirely to native idiom (already done for Java/C#/Go).

Drawbacks

  • Loses early validation. Strict-mode cross-file checks are gone. The host compiler catches the same errors, just one pipeline step later. Real cost: an extra ~10-30 seconds of host compile before the error surfaces, in cases where the user forgot an import. Judged acceptable.
  • Codemod work. Every fixture using @@import needs a mechanical rewrite to the native form. The matrix corpus has a small number of multi-file fixtures (Wave 7 persist × multi- system, RFC-0022 example fixtures); the sweep is bounded.
  • RFC turnover. RFC-0022 and RFC-0022.1 become Superseded just weeks after shipping. The cost is documentation churn. The benefit is that the language is honestly smaller.
  • Loss of project-wide dependency view. With @@import, framec had a complete view of cross-file dependencies in one parse. Future tooling (project linters, dependency graph visualizers) loses that handle. Counter-argument: the host language’s import system has the same information; tools should read host imports, not invent a Frame-specific layer.

Unresolved questions

  • Release pacing. Ship in 4.2.0 alongside other Oceans-Model consolidations? Or wait for a dedicated cleanup release?
  • E823 error message wording. Should the parse error name RFC-0024 directly, name the host-syntax equivalent for the current target, or both?
  • Codemod scope. Run only over tests/common/positive/, or also over frame-arcade and any other framec-using repositories? The codemod itself is target-uniform; the question is which trees to apply it to.

References