@xnoxs/flux-lang 3.5.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [4.0.0] — 2026-06-27
11
+
12
+ ### Added
13
+ - **`declare` keyword** — ambient type declarations, zero JS output
14
+ - `declare fn name(params): RetType` — declare external function signature
15
+ - `declare async fn name(params): RetType` — declare async external function
16
+ - `declare fn name(params) -> RetType` — alternative arrow return type syntax
17
+ - `declare val name: Type` — declare typed global constant
18
+ - `declare var name: Type` — declare typed mutable global
19
+ - `declare class Name` — declare external class
20
+ - Type checker registers all declared names in scope with their types
21
+ - Codegen emits **zero JavaScript** for all `declare` nodes (ambient only)
22
+ - Self-hosted compiler (lexer, parser, codegen, type-checker) fully updated
23
+ - 11 new tests in `tests/14_declare.test.flux`
24
+
25
+ ### Fixed
26
+ - **`flux fmt <directory>`** — previously crashed with `EISDIR`; now formats all `.flux` files in the given directory recursively
27
+ - **`flux publish`** — previously crashed with "package.json not found" in Flux projects using `flux.json`; now falls back correctly to `flux.json`
28
+
29
+ ### Changed
30
+ - Version bumped from `3.5.3` → `4.0.0`
31
+ - All version references in webapp (`app/ecosystem.flux`) updated to `v4.0.0`
32
+ - README updated with v4.0.0 release notes
33
+ - Docs: new **Ambient Declarations** section (`/docs#declare`) with full reference table, two code examples, callouts
34
+ - Docs sidebar: `Ambient Declarations` added under Type System and v3 Features
35
+ - Examples page: new **📣 declare** card
36
+ - Playground: new **declare** entry in Load Example dropdown
37
+ - Total tests: **113 passed** (102 language + 11 declare)
38
+
39
+ ---
40
+
10
41
  ## [3.4.4] — 2026-06-26
11
42
 
12
43
  ### Added
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
- # ⚡ Flux Lang v3.4.7
1
+ # ⚡ Flux Lang v4.0.0
2
2
 
3
3
  **Flux** is a modern programming language that transpiles to clean JavaScript — combining Python-style indentation, TypeScript-grade type safety, Rust-inspired pattern matching, and a rich standard library — all with **zero runtime overhead**.
4
4
 
5
+ > **v4.0.0 — `declare` keyword + full ambient declarations:** Flux now supports TypeScript-style ambient declarations. Declare external functions, variables, and classes without implementation — the type checker registers them in scope, codegen emits zero JS. Self-hosted compiler fully updated.
6
+
5
7
  > **v3.4.0 — Fully Self-Hosted:** Every component of the Flux compiler — lexer, parser, type checker, code generator, formatter, linter, bundler, package manager, and CLI — is now written entirely in Flux and compiled by itself. `node scripts/bootstrap.js` → "Bootstrap complete — Flux is self-hosting."
6
8
 
7
9
  ```
package/dist/flux-cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /*!
3
- * flux-lang v3.5.3
3
+ * flux-lang v4.0.0
4
4
  * Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.
5
5
  * (c) 2026 Flux Lang Contributors
6
6
  * Released under the MIT License
@@ -101,6 +101,7 @@ var require_lexer = __commonJS({
101
101
  VAL: "VAL",
102
102
  FN: "FN",
103
103
  RETURN: "RETURN",
104
+ DECLARE: "DECLARE",
104
105
  // Keywords — control flow
105
106
  IF: "IF",
106
107
  ELSE: "ELSE",
@@ -237,6 +238,7 @@ var require_lexer = __commonJS({
237
238
  val: T.VAL,
238
239
  fn: T.FN,
239
240
  return: T.RETURN,
241
+ declare: T.DECLARE,
240
242
  if: T.IF,
241
243
  else: T.ELSE,
242
244
  for: T.FOR,
@@ -1112,6 +1114,8 @@ var require_parser = __commonJS({
1112
1114
  if (decorators.length) n.decorators = decorators;
1113
1115
  return n;
1114
1116
  }
1117
+ case T.DECLARE:
1118
+ return this.parseDeclareDecl();
1115
1119
  case T.IF:
1116
1120
  return this.parseIf();
1117
1121
  case T.FOR:
@@ -1582,6 +1586,48 @@ var require_parser = __commonJS({
1582
1586
  }
1583
1587
  this.err("Expected -> or : after function signature");
1584
1588
  }
1589
+ // ── declare fn/val/var/class ───────────────────────────────────
1590
+ parseDeclareDecl() {
1591
+ const loc = this.eat(T.DECLARE);
1592
+ const tok = this.peek();
1593
+ if (tok.type === T.FN || tok.type === T.ASYNC) {
1594
+ const isAsync = tok.type === T.ASYNC;
1595
+ this.skip();
1596
+ if (isAsync) this.eat(T.FN);
1597
+ const KEYWORD_AS_NAME = /* @__PURE__ */ new Set([T.NEW, T.DELETE, T.FROM, T.AS, T.DEFAULT, T.IS, T.IN, T.TYPE]);
1598
+ const name = this.check(T.IDENT) || KEYWORD_AS_NAME.has(this.peek().type) ? this.skip().value : null;
1599
+ const params = this.parseParamList();
1600
+ let retType = null;
1601
+ if (this.check(T.ARROW)) {
1602
+ this.pos++;
1603
+ retType = this.parseTypeAnn();
1604
+ } else if (this.check(T.COLON)) {
1605
+ this.pos++;
1606
+ retType = this.parseTypeAnn();
1607
+ }
1608
+ this.skipNewlines();
1609
+ const decl = { type: "FnDecl", name, params, retType, body: [], inline: false, async: isAsync, loc: tok };
1610
+ return { type: "DeclareDecl", decl, loc };
1611
+ }
1612
+ if (tok.type === T.VAL || tok.type === T.VAR) {
1613
+ const kind = tok.type === T.VAR ? "var" : "val";
1614
+ this.skip();
1615
+ const name = this.eat(T.IDENT).value;
1616
+ let typeAnn = null;
1617
+ if (this.maybe(T.COLON)) typeAnn = this.parseTypeAnn();
1618
+ this.skipNewlines();
1619
+ const decl = { type: "VarDecl", kind, name, typeAnn, init: null, loc: tok };
1620
+ return { type: "DeclareDecl", decl, loc };
1621
+ }
1622
+ if (tok.type === T.CLASS) {
1623
+ this.skip();
1624
+ const name = this.eat(T.IDENT).value;
1625
+ this.skipNewlines();
1626
+ const decl = { type: "ClassDecl", name, fields: [], methods: [], loc: tok };
1627
+ return { type: "DeclareDecl", decl, loc };
1628
+ }
1629
+ this.err("Expected fn, async fn, val, var, or class after declare");
1630
+ }
1585
1631
  // ── async fn ──────────────────────────────────────────────────
1586
1632
  parseAsyncFn() {
1587
1633
  this.eat(T.ASYNC);
@@ -2693,6 +2739,9 @@ function _fmt(v, s) {
2693
2739
  return this.genInterfaceDecl(node);
2694
2740
  case "EnumDecl":
2695
2741
  return this.genEnumDecl(node);
2742
+ case "DeclareDecl":
2743
+ return;
2744
+ // ambient declaration — no JS output
2696
2745
  case "ExprStmt":
2697
2746
  return this.emit(this.genExpr(node.expr) + ";");
2698
2747
  default:
@@ -5695,6 +5744,20 @@ var require_type_checker = __commonJS({
5695
5744
  }
5696
5745
  break;
5697
5746
  }
5747
+ case "DeclareDecl": {
5748
+ const d = node.decl;
5749
+ if (d.type === "FnDecl") {
5750
+ const retType = d.retType ? parseAnnotation(d.retType) : null;
5751
+ const paramTypes = d.params.map((p) => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
5752
+ if (d.name) env.set(d.name, T_FN(paramTypes, retType || T_UNKNOWN));
5753
+ } else if (d.type === "VarDecl") {
5754
+ const t = d.typeAnn ? parseAnnotation(d.typeAnn) : T_UNKNOWN;
5755
+ env.set(d.name, t);
5756
+ } else if (d.type === "ClassDecl") {
5757
+ env.set(d.name, T_NAMED(d.name));
5758
+ }
5759
+ break;
5760
+ }
5698
5761
  case "TypeDecl":
5699
5762
  for (const v of node.variants) {
5700
5763
  if (v.fields.length === 0) env.set(v.name, T_NAMED(node.name));
@@ -7243,7 +7306,7 @@ var require_package = __commonJS({
7243
7306
  "package.json"(exports2, module2) {
7244
7307
  module2.exports = {
7245
7308
  name: "@xnoxs/flux-lang",
7246
- version: "3.5.3",
7309
+ version: "4.0.0",
7247
7310
  description: "Flux \u2014 A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.",
7248
7311
  main: "dist/flux.cjs.js",
7249
7312
  module: "dist/flux.esm.js",
@@ -8885,6 +8948,82 @@ var require_pkg = __commonJS({
8885
8948
  }
8886
8949
  }
8887
8950
  module2.exports.cmdInfo = cmdInfo;
8951
+ async function cmdUpgrade(opts) {
8952
+ var _a;
8953
+ const isCheck = (opts == null ? void 0 : opts.check) ?? false;
8954
+ const cwd = process.cwd();
8955
+ const pkg = readPackage(cwd);
8956
+ if (!pkg) {
8957
+ err("No flux.json found. Run: flux init");
8958
+ return;
8959
+ }
8960
+ const deps = pkg.dependencies ?? {};
8961
+ const devDeps = pkg.devDependencies ?? {};
8962
+ const all = { ...deps, ...devDeps };
8963
+ const names = Object.keys(all);
8964
+ if (names.length == 0) {
8965
+ info("No dependencies to upgrade");
8966
+ console.log();
8967
+ return;
8968
+ }
8969
+ if (isCheck) {
8970
+ console.log(clr2(C2.cyan, "\n Checking " + names.length + " package(s) for updates...\n"));
8971
+ } else {
8972
+ console.log(clr2(C2.cyan, "\n Upgrading " + names.length + " package(s) to latest...\n"));
8973
+ }
8974
+ let outdated = 0;
8975
+ let updated = 0;
8976
+ for (const name of names) {
8977
+ const current = all[name];
8978
+ const spinner = startSpinner("Checking " + clr2(C2.bold, name) + " ...");
8979
+ try {
8980
+ const info_ = await fetchJson(REGISTRY_URL + "/" + name);
8981
+ const latest = ((_a = info_["dist-tags"]) == null ? void 0 : _a.latest) ?? null;
8982
+ stopSpinner(spinner);
8983
+ if (!latest) {
8984
+ console.log(" " + clr2(C2.gray, "? ") + clr2(C2.bold, name) + clr2(C2.gray, " \u2014 could not resolve latest"));
8985
+ continue;
8986
+ }
8987
+ const currentClean = current.replace("^", "").replace("~", "");
8988
+ if (currentClean == latest) {
8989
+ console.log(" " + clr2(C2.green, "\u2713 ") + clr2(C2.bold, name) + clr2(C2.gray, " " + current + " (up to date)"));
8990
+ } else {
8991
+ outdated = outdated + 1;
8992
+ if (isCheck) {
8993
+ console.log(" " + clr2(C2.yellow, "\u2191 ") + clr2(C2.bold, name) + clr2(C2.gray, " " + current + " \u2192 ") + clr2(C2.green, "^" + latest));
8994
+ } else {
8995
+ const isDev = devDeps[name] != null;
8996
+ const depKey = isDev ? "devDependencies" : "dependencies";
8997
+ pkg[depKey][name] = "^" + latest;
8998
+ updated = updated + 1;
8999
+ console.log(" " + clr2(C2.green, "\u2713 ") + clr2(C2.bold, name) + clr2(C2.gray, " " + current + " \u2192 ") + clr2(C2.green, "^" + latest));
9000
+ }
9001
+ }
9002
+ } catch (e) {
9003
+ stopSpinner(spinner);
9004
+ console.log(" " + clr2(C2.red, "\u2717 ") + clr2(C2.bold, name) + clr2(C2.gray, " \u2014 " + e.message));
9005
+ }
9006
+ }
9007
+ console.log();
9008
+ if (isCheck) {
9009
+ if (outdated == 0) {
9010
+ ok("All packages are up to date");
9011
+ } else {
9012
+ console.log(clr2(C2.yellow, " " + outdated + " package(s) can be upgraded"));
9013
+ console.log(clr2(C2.gray, " Run ") + clr2(C2.yellow, "flux upgrade") + clr2(C2.gray, " to apply updates"));
9014
+ }
9015
+ } else {
9016
+ if (updated == 0) {
9017
+ ok("All packages are already up to date");
9018
+ } else {
9019
+ saveFluxJson(pkg, cwd);
9020
+ ok(updated + " package(s) updated in flux.json");
9021
+ console.log(clr2(C2.gray, " Run ") + clr2(C2.yellow, "flux install") + clr2(C2.gray, " to install updated versions"));
9022
+ }
9023
+ }
9024
+ console.log();
9025
+ }
9026
+ module2.exports.cmdUpgrade = cmdUpgrade;
8888
9027
  function cmdPublish2(opts) {
8889
9028
  const cwd = process.cwd();
8890
9029
  const pkg = readPackage(cwd);
@@ -9024,7 +9163,7 @@ function showHelp() {
9024
9163
  console.log(clr(C.gray, " flux watch app.flux"));
9025
9164
  console.log(clr(C.gray, " flux repl"));
9026
9165
  console.log();
9027
- console.log(clr(C.bold, "WHAT'S NEW in v3.0.0:"));
9166
+ console.log(clr(C.bold, `WHAT'S NEW in v${VERSION}:`));
9028
9167
  const news = [
9029
9168
  "Return type annotations \u2014 fn greet(name: String) -> String:",
9030
9169
  "Union types \u2014 val x: Int | String | Null",
@@ -9671,7 +9810,18 @@ function cmdLint(filePath) {
9671
9810
  if (exitCode !== 0) process.exit(exitCode);
9672
9811
  }
9673
9812
  function cmdFmt(filePath, opts) {
9674
- const { source, abs } = readFluxFile(filePath);
9813
+ const abs = path.resolve(filePath);
9814
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
9815
+ const entries = fs.readdirSync(abs).filter((f) => f.endsWith(".flux"));
9816
+ if (entries.length === 0) {
9817
+ console.log(clr(C.gray, ` No .flux files found in ${filePath}`));
9818
+ return;
9819
+ }
9820
+ let changed = 0;
9821
+ for (const entry of entries) cmdFmt(path.join(filePath, entry), opts);
9822
+ return;
9823
+ }
9824
+ const { source } = readFluxFile(filePath);
9675
9825
  const { format } = require_formatter();
9676
9826
  const output = format(source);
9677
9827
  if (output === source) {
@@ -9938,9 +10088,19 @@ ${e.message}`);
9938
10088
  banner();
9939
10089
  console.log(clr(C.bold, ` Release Workflow${isDry ? clr(C.yellow, " [DRY RUN]") : ""}
9940
10090
  `));
9941
- const pkgPath = path.resolve("package.json");
9942
10091
  const changelogPath = path.resolve("CHANGELOG.md");
9943
- if (!fs.existsSync(pkgPath)) fail("package.json not found");
10092
+ const npmPkgPath = path.resolve("package.json");
10093
+ const fluxPkgPath = path.resolve("flux.json");
10094
+ let pkgPath, pkgFile;
10095
+ if (fs.existsSync(npmPkgPath)) {
10096
+ pkgPath = npmPkgPath;
10097
+ pkgFile = "package.json";
10098
+ } else if (fs.existsSync(fluxPkgPath)) {
10099
+ pkgPath = fluxPkgPath;
10100
+ pkgFile = "flux.json";
10101
+ } else {
10102
+ fail("No package.json or flux.json found \u2014 run: flux init");
10103
+ }
9944
10104
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
9945
10105
  const oldVersion = pkg.version;
9946
10106
  const newVersion = bumpVersion(oldVersion, bumpType);
@@ -10002,19 +10162,19 @@ ${e.message}`);
10002
10162
  step("Confirm release");
10003
10163
  const go = await confirm(`Release ${clr(C.bold, pkg.name + "@" + newVersion)} (${tagName})?`);
10004
10164
  if (!go) fail("Release cancelled");
10005
- step(`Bumping package.json: ${oldVersion} \u2192 ${newVersion}`);
10165
+ step(`Bumping ${pkgFile}: ${oldVersion} \u2192 ${newVersion}`);
10006
10166
  if (!isDry) {
10007
10167
  pkg.version = newVersion;
10008
10168
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
10009
10169
  } else {
10010
- dry(`package.json: "version": "${oldVersion}" \u2192 "${newVersion}"`);
10170
+ dry(`${pkgFile}: "version": "${oldVersion}" \u2192 "${newVersion}"`);
10011
10171
  }
10012
- ok(`package.json updated`);
10172
+ ok(`${pkgFile} updated`);
10013
10173
  step("Updating CHANGELOG.md");
10014
10174
  const clUpdated = updateChangelog(newVersion, changelogPath);
10015
10175
  if (clUpdated) ok(`CHANGELOG.md updated \u2014 [${newVersion}] section created`);
10016
10176
  step("Creating git commit");
10017
- run("git add package.json CHANGELOG.md");
10177
+ run(`git add ${pkgFile} CHANGELOG.md`);
10018
10178
  run(`git commit -m "chore: release ${tagName}"`);
10019
10179
  ok(`Committed: chore: release ${tagName}`);
10020
10180
  step(`Creating git tag: ${tagName}`);
@@ -10046,7 +10206,7 @@ ${e.message}`);
10046
10206
  console.log(clr(C.green, C.bold + ` \u2713 Released: ${pkg.name}@${newVersion}` + C.reset));
10047
10207
  console.log();
10048
10208
  console.log(clr(C.gray, " Summary:"));
10049
- console.log(` ${clr(C.cyan, `package.json`)} version \u2192 ${newVersion}`);
10209
+ console.log(` ${clr(C.cyan, pkgFile)} version \u2192 ${newVersion}`);
10050
10210
  if (clUpdated)
10051
10211
  console.log(` ${clr(C.cyan, `CHANGELOG.md`)} [${newVersion}] \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
10052
10212
  console.log(` ${clr(C.cyan, `git tag`)} ${tagName}`);
package/dist/flux.cjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * flux-lang v3.5.3
2
+ * flux-lang v4.0.0
3
3
  * Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.
4
4
  * (c) 2026 Flux Lang Contributors
5
5
  * Released under the MIT License
@@ -30,6 +30,7 @@ var require_lexer = __commonJS({
30
30
  VAL: "VAL",
31
31
  FN: "FN",
32
32
  RETURN: "RETURN",
33
+ DECLARE: "DECLARE",
33
34
  // Keywords — control flow
34
35
  IF: "IF",
35
36
  ELSE: "ELSE",
@@ -166,6 +167,7 @@ var require_lexer = __commonJS({
166
167
  val: T.VAL,
167
168
  fn: T.FN,
168
169
  return: T.RETURN,
170
+ declare: T.DECLARE,
169
171
  if: T.IF,
170
172
  else: T.ELSE,
171
173
  for: T.FOR,
@@ -1041,6 +1043,8 @@ var require_parser = __commonJS({
1041
1043
  if (decorators.length) n.decorators = decorators;
1042
1044
  return n;
1043
1045
  }
1046
+ case T.DECLARE:
1047
+ return this.parseDeclareDecl();
1044
1048
  case T.IF:
1045
1049
  return this.parseIf();
1046
1050
  case T.FOR:
@@ -1511,6 +1515,48 @@ var require_parser = __commonJS({
1511
1515
  }
1512
1516
  this.err("Expected -> or : after function signature");
1513
1517
  }
1518
+ // ── declare fn/val/var/class ───────────────────────────────────
1519
+ parseDeclareDecl() {
1520
+ const loc = this.eat(T.DECLARE);
1521
+ const tok = this.peek();
1522
+ if (tok.type === T.FN || tok.type === T.ASYNC) {
1523
+ const isAsync = tok.type === T.ASYNC;
1524
+ this.skip();
1525
+ if (isAsync) this.eat(T.FN);
1526
+ const KEYWORD_AS_NAME = /* @__PURE__ */ new Set([T.NEW, T.DELETE, T.FROM, T.AS, T.DEFAULT, T.IS, T.IN, T.TYPE]);
1527
+ const name = this.check(T.IDENT) || KEYWORD_AS_NAME.has(this.peek().type) ? this.skip().value : null;
1528
+ const params = this.parseParamList();
1529
+ let retType = null;
1530
+ if (this.check(T.ARROW)) {
1531
+ this.pos++;
1532
+ retType = this.parseTypeAnn();
1533
+ } else if (this.check(T.COLON)) {
1534
+ this.pos++;
1535
+ retType = this.parseTypeAnn();
1536
+ }
1537
+ this.skipNewlines();
1538
+ const decl = { type: "FnDecl", name, params, retType, body: [], inline: false, async: isAsync, loc: tok };
1539
+ return { type: "DeclareDecl", decl, loc };
1540
+ }
1541
+ if (tok.type === T.VAL || tok.type === T.VAR) {
1542
+ const kind = tok.type === T.VAR ? "var" : "val";
1543
+ this.skip();
1544
+ const name = this.eat(T.IDENT).value;
1545
+ let typeAnn = null;
1546
+ if (this.maybe(T.COLON)) typeAnn = this.parseTypeAnn();
1547
+ this.skipNewlines();
1548
+ const decl = { type: "VarDecl", kind, name, typeAnn, init: null, loc: tok };
1549
+ return { type: "DeclareDecl", decl, loc };
1550
+ }
1551
+ if (tok.type === T.CLASS) {
1552
+ this.skip();
1553
+ const name = this.eat(T.IDENT).value;
1554
+ this.skipNewlines();
1555
+ const decl = { type: "ClassDecl", name, fields: [], methods: [], loc: tok };
1556
+ return { type: "DeclareDecl", decl, loc };
1557
+ }
1558
+ this.err("Expected fn, async fn, val, var, or class after declare");
1559
+ }
1514
1560
  // ── async fn ──────────────────────────────────────────────────
1515
1561
  parseAsyncFn() {
1516
1562
  this.eat(T.ASYNC);
@@ -2622,6 +2668,9 @@ function _fmt(v, s) {
2622
2668
  return this.genInterfaceDecl(node);
2623
2669
  case "EnumDecl":
2624
2670
  return this.genEnumDecl(node);
2671
+ case "DeclareDecl":
2672
+ return;
2673
+ // ambient declaration — no JS output
2625
2674
  case "ExprStmt":
2626
2675
  return this.emit(this.genExpr(node.expr) + ";");
2627
2676
  default:
@@ -5624,6 +5673,20 @@ var require_type_checker = __commonJS({
5624
5673
  }
5625
5674
  break;
5626
5675
  }
5676
+ case "DeclareDecl": {
5677
+ const d = node.decl;
5678
+ if (d.type === "FnDecl") {
5679
+ const retType = d.retType ? parseAnnotation(d.retType) : null;
5680
+ const paramTypes = d.params.map((p) => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
5681
+ if (d.name) env.set(d.name, T_FN(paramTypes, retType || T_UNKNOWN));
5682
+ } else if (d.type === "VarDecl") {
5683
+ const t = d.typeAnn ? parseAnnotation(d.typeAnn) : T_UNKNOWN;
5684
+ env.set(d.name, t);
5685
+ } else if (d.type === "ClassDecl") {
5686
+ env.set(d.name, T_NAMED(d.name));
5687
+ }
5688
+ break;
5689
+ }
5627
5690
  case "TypeDecl":
5628
5691
  for (const v of node.variants) {
5629
5692
  if (v.fields.length === 0) env.set(v.name, T_NAMED(node.name));
package/dist/flux.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * flux-lang v3.5.3
2
+ * flux-lang v4.0.0
3
3
  * Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.
4
4
  * (c) 2026 Flux Lang Contributors
5
5
  * Released under the MIT License
@@ -35,6 +35,7 @@ var require_lexer = __commonJS({
35
35
  VAL: "VAL",
36
36
  FN: "FN",
37
37
  RETURN: "RETURN",
38
+ DECLARE: "DECLARE",
38
39
  // Keywords — control flow
39
40
  IF: "IF",
40
41
  ELSE: "ELSE",
@@ -171,6 +172,7 @@ var require_lexer = __commonJS({
171
172
  val: T.VAL,
172
173
  fn: T.FN,
173
174
  return: T.RETURN,
175
+ declare: T.DECLARE,
174
176
  if: T.IF,
175
177
  else: T.ELSE,
176
178
  for: T.FOR,
@@ -1046,6 +1048,8 @@ var require_parser = __commonJS({
1046
1048
  if (decorators.length) n.decorators = decorators;
1047
1049
  return n;
1048
1050
  }
1051
+ case T.DECLARE:
1052
+ return this.parseDeclareDecl();
1049
1053
  case T.IF:
1050
1054
  return this.parseIf();
1051
1055
  case T.FOR:
@@ -1516,6 +1520,48 @@ var require_parser = __commonJS({
1516
1520
  }
1517
1521
  this.err("Expected -> or : after function signature");
1518
1522
  }
1523
+ // ── declare fn/val/var/class ───────────────────────────────────
1524
+ parseDeclareDecl() {
1525
+ const loc = this.eat(T.DECLARE);
1526
+ const tok = this.peek();
1527
+ if (tok.type === T.FN || tok.type === T.ASYNC) {
1528
+ const isAsync = tok.type === T.ASYNC;
1529
+ this.skip();
1530
+ if (isAsync) this.eat(T.FN);
1531
+ const KEYWORD_AS_NAME = /* @__PURE__ */ new Set([T.NEW, T.DELETE, T.FROM, T.AS, T.DEFAULT, T.IS, T.IN, T.TYPE]);
1532
+ const name = this.check(T.IDENT) || KEYWORD_AS_NAME.has(this.peek().type) ? this.skip().value : null;
1533
+ const params = this.parseParamList();
1534
+ let retType = null;
1535
+ if (this.check(T.ARROW)) {
1536
+ this.pos++;
1537
+ retType = this.parseTypeAnn();
1538
+ } else if (this.check(T.COLON)) {
1539
+ this.pos++;
1540
+ retType = this.parseTypeAnn();
1541
+ }
1542
+ this.skipNewlines();
1543
+ const decl = { type: "FnDecl", name, params, retType, body: [], inline: false, async: isAsync, loc: tok };
1544
+ return { type: "DeclareDecl", decl, loc };
1545
+ }
1546
+ if (tok.type === T.VAL || tok.type === T.VAR) {
1547
+ const kind = tok.type === T.VAR ? "var" : "val";
1548
+ this.skip();
1549
+ const name = this.eat(T.IDENT).value;
1550
+ let typeAnn = null;
1551
+ if (this.maybe(T.COLON)) typeAnn = this.parseTypeAnn();
1552
+ this.skipNewlines();
1553
+ const decl = { type: "VarDecl", kind, name, typeAnn, init: null, loc: tok };
1554
+ return { type: "DeclareDecl", decl, loc };
1555
+ }
1556
+ if (tok.type === T.CLASS) {
1557
+ this.skip();
1558
+ const name = this.eat(T.IDENT).value;
1559
+ this.skipNewlines();
1560
+ const decl = { type: "ClassDecl", name, fields: [], methods: [], loc: tok };
1561
+ return { type: "DeclareDecl", decl, loc };
1562
+ }
1563
+ this.err("Expected fn, async fn, val, var, or class after declare");
1564
+ }
1519
1565
  // ── async fn ──────────────────────────────────────────────────
1520
1566
  parseAsyncFn() {
1521
1567
  this.eat(T.ASYNC);
@@ -2627,6 +2673,9 @@ function _fmt(v, s) {
2627
2673
  return this.genInterfaceDecl(node);
2628
2674
  case "EnumDecl":
2629
2675
  return this.genEnumDecl(node);
2676
+ case "DeclareDecl":
2677
+ return;
2678
+ // ambient declaration — no JS output
2630
2679
  case "ExprStmt":
2631
2680
  return this.emit(this.genExpr(node.expr) + ";");
2632
2681
  default:
@@ -5629,6 +5678,20 @@ var require_type_checker = __commonJS({
5629
5678
  }
5630
5679
  break;
5631
5680
  }
5681
+ case "DeclareDecl": {
5682
+ const d = node.decl;
5683
+ if (d.type === "FnDecl") {
5684
+ const retType = d.retType ? parseAnnotation(d.retType) : null;
5685
+ const paramTypes = d.params.map((p) => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
5686
+ if (d.name) env.set(d.name, T_FN(paramTypes, retType || T_UNKNOWN));
5687
+ } else if (d.type === "VarDecl") {
5688
+ const t = d.typeAnn ? parseAnnotation(d.typeAnn) : T_UNKNOWN;
5689
+ env.set(d.name, t);
5690
+ } else if (d.type === "ClassDecl") {
5691
+ env.set(d.name, T_NAMED(d.name));
5692
+ }
5693
+ break;
5694
+ }
5632
5695
  case "TypeDecl":
5633
5696
  for (const v of node.variants) {
5634
5697
  if (v.fields.length === 0) env.set(v.name, T_NAMED(node.name));