Tya v0.44 Migration Guide

This guide explains how to migrate Tya code from the v0.43 module shape to the v0.44 class-oriented namespace model.

The full specification lives in SPEC.md. This document is practical: it walks the common migration cases with before/after examples.

Why migrate

v0.44 replaces the module name + free functions shape with a class-oriented namespace model. The wins:

Two file kinds

Filename starts with Kind Role Importable
Uppercase letter Class file Library member Yes
Lowercase letter Script file Entry; runs via tya run No

Class file: Request.tya

class Request
  init = url ->
    @url = url

  get = ->
    # ...

The PascalCase filename matches the public class name. Additional class declarations in the same file are private to that file.

Script file: client.tya

import net/http

request = http.Request("http://example.com")
print(request.get())

Lowercase filename, top-level statements run from the top.

Migrating a free-function module

Before (v0.43)

stdlib/path.tya:

module path
  basename = value -> ...
  dirname = value -> ...
  join = parts -> ...

Caller:

import path
print(path.join(["tmp", "x.txt"]))

After (v0.44)

stdlib/path/Path.tya:

class Path
  @@basename = value -> ...
  @@dirname = value -> ...
  @@join = parts -> ...

Caller:

import path
print(path.Path.join(["tmp", "x.txt"]))

Mechanical recipe:

  1. Move stdlib/<name>.tya to stdlib/<name>/<Name>.tya (capitalize the leaf as the class name).
  2. Replace module <name> with class <Name>.
  3. Prefix every member function with @@. Leave constants without @@ prefix only if they are class variables; in v0.44 the convention is @@CONSTANT_NAME = ....
  4. Update all callers from <name>.X(...) to <name>.<Name>.X(...).
  5. Internal cross-method calls (path.basename(x) from inside another path member) become Path.basename(x).

Migrating between class methods within a package

Inside a single package, sibling classes in different files call each other by bare PascalCase name:

api/v1/Client.tya:

class Client
  fetch = url ->
    request = Request(url)        # bare; resolves to v1.Request
    response = Response(200)      # bare; resolves to v1.Response
    "{request.method()} -> {response.status()}"

api/v1/Request.tya:

class Request
  init = url ->
    @url = url

  method = ->
    "GET"

The bare name resolution is automatic for sibling classes within the same package directory. From outside the package, you still write v1.Request(url), v1.Response(200), etc.

Same-directory siblings for entry scripts

If your project has a script entry plus a few PascalCase class files in the same directory, the entry sees the classes without an explicit import:

Greeter.tya
main.tya

Greeter.tya:

class Greeter
  init = name ->
    @name = name

  greet = ->
    "Hello, {@name}"

main.tya:

greeter = Greeter("komagata")     # no import needed
print(greeter.greet())

tya run main.tya prints Hello, komagata.

Package directory rules

Imports

# Cross-package import (always required)
import net/http

# Path restrictions: forbidden
import /etc/passwd       # absolute paths
import ./local           # leading dot
import ../parent         # leading dotdot
import foo/../bar        # embedded dotdot
import foo//bar          # empty segment
import foo/Bar           # PascalCase terminal segment

Resolution order (unchanged from v0.43): the importing file’s directory, each TYA_PATH entry, then stdlib/.

Entry execution (tya run)

tya run accepts only script files (lowercase filename). Running a class file gives a structured diagnostic:

$ tya run Hello.tya
Hello.tya is a class file; tya run accepts only script files (lowercase filename)

Class files are library-only. To make a class file runnable, write a script file next to it that instantiates and uses it:

Hello.tya          # class Hello with whatever methods
main.tya           # script: h = Hello(); h.run()
$ tya run main.tya

Public and private classes per file

Every .tya file has exactly one public class:

Other classes in the same file are private to that file. They follow the same PascalCase naming rule and may use extends, implements, abstract, final, and override like public classes.

# Server.tya
class Connection                 # private to Server.tya
  init = socket ->
    @socket = socket

class Server                     # public; matches filename
  init = port ->
    @port = port

  accept = socket ->
    Connection(socket)           # bare reference inside the same file

Limitation note: v0.44 does not yet enforce cross-file privacy at compile time. Another file in the same package can still reference Connection if it knows the name. Full enforcement is tracked under M5 in ROADMAP.md and pairs with M8 self-host migration.

Class member conventions (unchanged)

CLI commands and file kinds

Command Script file Class file Notes
tya run yes no Entry only; class file rejected with [TYA-E0850].
tya build yes no Entry only.
tya check yes yes Validates class file via CheckClassFile in isolation.
tya format yes yes Round-trips canonical syntax.
tya test yes (test) Operates on *_test.tya script files.
tya --tokens yes yes Lexer dump.
tya --emit-c yes yes Class file emits a standalone-compilable C with trivial main.
tya --check-unused yes yes Strict-pass diagnostics on class files too.

Practical examples:

tya format src/Greeter.tya     # round-trip a class file
tya check src/Greeter.tya      # validate a class file
tya --emit-c src/Greeter.tya   # see the C codegen for a class file
tya run main.tya               # run an entry script

CLI arguments

CLI arguments are not delivered as a main parameter. They are read through the standard library:

import os

for arg in os.Os.args()
  print(arg)

What the migration removes

What stays the same

Migration status (M6)

Stdlib packages migrated to the new shape:

Stdlib packages held back:

See ROADMAP.md for the full milestone breakdown.