tova 0.3.9 → 0.4.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/bin/tova.js CHANGED
@@ -1443,7 +1443,7 @@ shared {
1443
1443
  }
1444
1444
 
1445
1445
  server {
1446
- fn get_message() -> Message {
1446
+ fn get_message() {
1447
1447
  Message("Hello from Tova!", Date.new().toLocaleTimeString())
1448
1448
  }
1449
1449
 
@@ -1560,7 +1560,7 @@ client {
1560
1560
  content: name => `// ${name} — Built with Tova
1561
1561
 
1562
1562
  server {
1563
- fn health() -> { status: String } {
1563
+ fn health() {
1564
1564
  { status: "ok" }
1565
1565
  }
1566
1566
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tova",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "Tova — a modern programming language that transpiles to JavaScript, unifying frontend and backend",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -389,6 +389,8 @@ export class Analyzer {
389
389
  if (sym.extern) continue;
390
390
  if (sym._variantOf) continue; // ADT variant constructors
391
391
  if (name === 'main') continue;
392
+ // Server functions are exposed as RPC endpoints callable from client blocks
393
+ if (scope.context === 'server') continue;
392
394
 
393
395
  if (!sym.used && sym.loc && sym.loc.line > 0) {
394
396
  this.warn(`Function '${name}' is declared but never used`, sym.loc, "prefix with _ to suppress", {
@@ -2418,14 +2420,21 @@ export class Analyzer {
2418
2420
  // preventing silent shadowing of immutable bindings within the same function.
2419
2421
  _lookupAssignTarget(name) {
2420
2422
  let scope = this.currentScope;
2423
+ let crossedFunction = false;
2421
2424
  while (scope) {
2422
2425
  const sym = scope.symbols.get(name);
2423
- if (sym) return sym;
2424
- // Stop after checking a function or top-level scope (don't cross function boundaries)
2425
- if (scope.context === 'function' || scope.context === 'module' ||
2426
- scope.context === 'server' || scope.context === 'client' || scope.context === 'shared') {
2427
- break;
2426
+ if (sym) {
2427
+ // After crossing a function boundary, only resolve to mutable symbols (var/state)
2428
+ // so that inner functions can reassign outer var/state but not shadow immutables
2429
+ if (crossedFunction && !sym.mutable) return null;
2430
+ return sym;
2431
+ }
2432
+ // Track when we cross a function boundary
2433
+ if (scope.context === 'function') {
2434
+ crossedFunction = true;
2428
2435
  }
2436
+ // Stop at module level
2437
+ if (scope.context === 'module') break;
2429
2438
  scope = scope.parent;
2430
2439
  }
2431
2440
  return null;
@@ -111,15 +111,8 @@ export function installClientAnalyzer(AnalyzerClass) {
111
111
  }
112
112
  };
113
113
 
114
- AnalyzerClass.prototype.visitJSXElement = function(node) {
115
- for (const attr of node.attributes) {
116
- if (attr.type === 'JSXSpreadAttribute') {
117
- this.visitExpression(attr.expression);
118
- } else {
119
- this.visitExpression(attr.value);
120
- }
121
- }
122
- for (const child of node.children) {
114
+ AnalyzerClass.prototype._visitJSXChildren = function(children) {
115
+ for (const child of children) {
123
116
  if (child.type === 'JSXElement') {
124
117
  this.visitJSXElement(child);
125
118
  } else if (child.type === 'JSXFragment') {
@@ -132,26 +125,30 @@ export function installClientAnalyzer(AnalyzerClass) {
132
125
  this.visitJSXIf(child);
133
126
  } else if (child.type === 'JSXMatch') {
134
127
  this.visitJSXMatch(child);
128
+ } else if (child.type === 'JSXText') {
129
+ // JSXText wraps a TemplateLiteral/StringLiteral in its .value
130
+ // Visit it so identifiers in interpolated strings are marked as used
131
+ if (child.value) this.visitExpression(child.value);
132
+ } else if (child.type) {
133
+ // Other expression-type children
134
+ this.visitExpression(child);
135
135
  }
136
136
  }
137
137
  };
138
138
 
139
- AnalyzerClass.prototype.visitJSXFragment = function(node) {
140
- for (const child of node.children) {
141
- if (child.type === 'JSXElement') {
142
- this.visitJSXElement(child);
143
- } else if (child.type === 'JSXFragment') {
144
- this.visitJSXFragment(child);
145
- } else if (child.type === 'JSXExpression') {
146
- this.visitExpression(child.expression);
147
- } else if (child.type === 'JSXFor') {
148
- this.visitJSXFor(child);
149
- } else if (child.type === 'JSXIf') {
150
- this.visitJSXIf(child);
151
- } else if (child.type === 'JSXMatch') {
152
- this.visitJSXMatch(child);
139
+ AnalyzerClass.prototype.visitJSXElement = function(node) {
140
+ for (const attr of node.attributes) {
141
+ if (attr.type === 'JSXSpreadAttribute') {
142
+ this.visitExpression(attr.expression);
143
+ } else {
144
+ this.visitExpression(attr.value);
153
145
  }
154
146
  }
147
+ this._visitJSXChildren(node.children);
148
+ };
149
+
150
+ AnalyzerClass.prototype.visitJSXFragment = function(node) {
151
+ this._visitJSXChildren(node.children);
155
152
  };
156
153
 
157
154
  AnalyzerClass.prototype.visitJSXFor = function(node) {
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/embed-runtime.js — do not edit
2
- export const VERSION = "0.3.9";
2
+ export const VERSION = "0.4.0";