Skip to content

v0.29.0

Latest

Choose a tag to compare

@github-actions github-actions released this 12 May 13:23
· 19 commits to main since this release
Immutable release. Only release title and notes can be modified.

New Features

Optional type annotations

You can now annotate function parameters and return types when you want to. Annotations are optional everywhere — existing code keeps working unchanged.

def add(a : Integer, b : Integer) : Integer
  return a + b
end

def greet(name : String) : String
  return "hello, " + name
end

square = fn(n : Integer) : Integer
  return n * n
end

The recognised names mirror what type_of() returns: Integer, Float, String, Bool, Array, Hash, Nil, Any. Whitespace around : is allowed but not required.

What this gets you:

  • Faster code. Annotated Integer/Float/String/Bool params and returns compile to typed Go signatures (e.g. func(int, int) int), skipping the boxing/unboxing that dynamic code needs.
  • Earlier errors. Misspelled type names (integer, Strring) fail at compile time with a clear file:line message; common lowercase shorthands like int get a "did you mean" hint.
  • Visibility. rugo emit --stats script.rugo now reports annotation coverage alongside what type inference figured out on its own, so you can see where a couple of annotations would help most.

Mix annotated and unannotated freely — there's no requirement to type everything, and no runtime cost to typing some of it.

See docs/language.md for the full reference and examples/typed_functions.rugo for a working tour.

Variable type annotations

Local variables can be annotated too, with the same name : type = expr syntax:

name  : String  = "alice"
count : Integer = 0

The annotation sticks for the rest of the scope. Later assignments must match, or the compiler tells you exactly what went wrong:

count = "oops"
# error: cannot assign String value to variable 'count' declared as Integer

Use : Any when you genuinely want a variable to hold different types over time:

value : Any = 42
value = "now a string"   # ok

Compile-time type-mismatch detection

When the types involved are concretely known, Rugo now flags clear conflicts at compile time instead of producing a confusing Go-level message or a runtime panic. Three flavours are covered:

Bad assignments inside annotated function bodies:

def f(a : Integer)
  a = "hello"
  # error: script.rugo:2: cannot assign String value
  #        to parameter 'a' declared as Integer
end

Bad return values:

def lookup(id : Integer) : Integer
  if id < 0
    return "negative"
    # error: cannot return String value from
    #        function declared returning Integer
  end
  return id
end

Bad arguments at call sites — works for literal values and for variables whose type the compiler can track:

def add(a : Integer, b : Integer) : Integer
  return a + b
end

puts add("xyz", 2)
# error: cannot pass String literal as argument 1 to 'add'
#        (parameter 'a' declared as Integer)

x = 42
def greet(name : String)
  puts "hi #{name}"
end
greet(x)
# error: cannot pass Integer value as argument 1 to 'greet'
#        (parameter 'name' declared as String)

Rugo follows the type of each variable through if, case, while, and for, so the same checks apply across branches and reassignments. When a variable could legitimately hold one of several types depending on which branch ran, the compiler only complains if every possible type would be wrong — keeping real code quiet while still catching genuine mistakes.

Compatibility rules match what the runtime does: numeric types (Integer and Float) are mutually compatible, and Any parameters accept any value. Pass --no-infer to disable the check.

Improvements

  • Nil as a type annotation. def f() : Nil and def g(x : Nil) now parse correctly, matching what the documentation already described.
  • Annotation coverage in --stats. rugo emit --stats reports how many params/returns carry annotations, alongside the existing inferred-type stats.

Bug Fixes

  • test.run("rugo …") uses the rugo under test. RATS tests that invoke rugo through the test module now consistently use the binary running the suite, not a system-installed one that happened to be on PATH. Tests can reliably assert on rugo emit, rugo build, and other CLI output.