Skip to content

v0.35.0

Choose a tag to compare

@emil14 emil14 released this 28 Feb 20:43
· 492 commits to main since this release
5434d4d

Pragmatic Power

The previous "Back to Dataflow" release v0.34 has cleaned up the language surface and removed a lot of accidental complexity.

Current "Pragmatic Power" releasev0.35 is what comes next: making that simpler core actually pleasant and powerful in real programs. This release is more about real leverage.

🌟 Summary

  • New bytes builtin data-type for effective IO operations. We no longer pretend that all bytes are strings.
  • Idiomatic type convertors in stdlib (streams/dicts/strings/bytes).
  • Stdlib port naming was standardized (data/res/err/sig defaults) across many components to make APIs more predictable.
  • Even stricter syntax (nodes now must have explicit names in component definition).
  • The maybe type is no longer special case for type-system, it's just union now.
  • Removed deferred connections syntax and switched to explicit lock wiring.
  • Array-bypass syntax was finalized as [*] (instead of =>), which is much clearer and less magical.
  • Neva LSP was moved to separate repository for maintainability.
  • Core LSP language features landed (huge step for daily DX in editors).
  • Fixed old issue with zombie processes after e2e by improving pkg/e2e package for end-to-end testing.

🧠 Language & Semantics

First-class bytes

bytes is now a first-class builtin type.

Minimal snippet (nodes + network only):

read_all io.ReadAll?
bytes_to_string strings.FromBytes
println fmt.Println<any>?
---
:start -> 'bytes_roundtrip.txt' -> read_all:filename -> bytes_to_string -> println -> :stop

A concrete stdlib API example using bytes:

#extern(write_all)
pub def WriteAll(filename string, data bytes) (res any, err error)

Node (top-level) names are now required

It makes code more consistent and easier to reason about for LLMs.

// before (now invalid)
import { fmt }

def Main(start any) (stop any) {
    fmt.Println
    ...
}
// after
import { fmt }

def Main(start any) (stop any) {
    println fmt.Println
    ...
}

maybe/error modeling cleanup

maybe<T> is now regular tagged-union modeling in std/builtin.

pub type maybe<T> union {
    Some T
    None
}

pub type error struct {
    text string
    child maybe<error>
}

No special type-system path is needed for optional/error chaining.

Deferred connections removed

We finished the remaining language cleanup from v0.34 by removing deferred connection syntax.

Before, you could write sugar like a -> { b -> c }, where delivery from b to c was deferred by a. It worked, but

  1. It added one more connection form i.e. made language more complex
  2. It made dataflow less obvious (it's not clear that what's deferred is receiving by c and not sending by b, which is clear using explicit locks - what desugarer was doing under the hood)
  3. It made 1-1 mapping from source code to visual node editor impossible, which was the most critical problem among all 3. The best way to visualize deferred connection was to "desugar it" at the level of the visual editor, which means we would desugar it two times, at the different edges of the compilation spectrum, which... Doesn't feel right, let's say.

So now this is explicit wiring via builtin.Lock:

lock Lock<string>
---
a -> lock:sig
b -> lock:data
lock -> c

Array-bypass syntax finalized as [*]

Array bypass used to use =>. Now it is explicit port-slot wildcard syntax on both sides:

// before
in:items => out:items

// after
in:items[*] -> out:items[*]

This might seem like a opinionated change but actually it's just simplification we didn't see possible before - at AST level we used to have 2 kinds of connection, a normal one and array bypass one. Now array bypass is just a special case of normal connection where array slot index is * (which is encoded as 255 - reserved uint8 value). So all connections are "normal" now. I.e. there are just "connections". Also [*] feels more consistent with [i] (e.g. [0]) rather than using different kind of arrow =>.

📦 Standard Library: Conversion Toolkit

Stdlib port naming standardization

A lot of stdlib APIs were normalized to follow port naming convention with (data, res, err, sig) with boundary exceptions only when domain naming adds real value. The convention itself was finalized in the docs/style_guide.md document.

This is not a flashy feature, but it helps to form idiomatic conventions for the language and its standard library. This particular change should make it a little bit easier to reason about the port names. We expect you to just follow the convention without asking yourself a lot about "how do I name this port?". Also should help LLMs with codegen predictability.

New / improved conversion path components

  • streams.FromString(data string) (res stream<string>)
  • streams.FromDict<T>(data dict<T>) (res stream<DictEntry<T>>)
  • dicts.FromStream<T>(data stream<DictEntry<T>>) (res dict<T>)
  • strings.FromBytes(data bytes) (res string)

This introduces idiomatic convention for data-type convertors. We have decided to continue follow "small core" philosophy and made type convertors simple components rather than language feature.

Example: dict -> stream

const dict_value dict<string> = {
    a: 'one',
    b: 'two'
}

...

dict_to_stream streams.FromDict<string>
for_each_println streams.ForEach<DictEntry<string>>{fmt.Println<any>}?
wait streams.Wait
---
:start -> $dict_value -> dict_to_stream -> for_each_println -> wait -> :stop

Example: stream -> dict (last write wins)

const dict_entries list<DictEntry<string>> = [
    { key: 'dup', value: 'one' },
    { key: 'dup', value: 'forty-two' }
]

...

list_to_stream streams.FromList<DictEntry<string>>
stream_to_dict dicts.FromStream<string>
println fmt.Println<any>?
---
:start -> $dict_entries -> list_to_stream -> stream_to_dict -> println -> :stop

Scalar conversions

Builtin scalar converters now explicitly document intent (aligned with Go):

  • Int(float) -> int (truncate toward zero)
  • Float(int) -> float
  • String(int) -> string (Unicode code point)

This gives a sane, predictable baseline while keeping non-total parsing/formatting in stdlib (strconv style APIs).

Bytes(string) -> bytes and String(bytes) -> string in builtin are in progress.

⚙️ Tooling & Architecture

Go 1.26 migration + go fix discipline

Repository now targets:

  • go 1.26
  • toolchain go1.26.0

CI now enforces go fix ./... cleanliness. If you haven't read about go fix then do it. It's awesome tool that automatically rewrites legacy Go code to its modern version respecting language and stdlib changes. Now every Neva release language is going to be better and better also because of this, among with many-many other reasons.

LSP was moved + Refactoring

A lot of groundwork landed to make this split working:

  • public pkg/ast, pkg/core, pkg/indexer, pkg/typesystem,
  • in-repo cmd/lsp removed,
  • canonical LSP implementation lives in nevalang/neva-lsp.

This is important for velocity in neva-lsp and vscode-neva: compiler core stays focused, language tooling can evolve in its own repo.

pkg/* APIs are now usable from external Go modules

Expose public APIs for external LSP extraction means you can now import Neva AST/typesystem packages directly from another Go module.

Minimal example:

package main

import (
	"fmt"

	src "github.com/nevalang/neva/pkg/ast"
	ts "github.com/nevalang/neva/pkg/typesystem"
)

func main() {
	var _ src.Component
	var _ ts.Expr
	fmt.Println("neva ast/typesystem imported successfully")
}

LSP core language features (major milestone)

Core editor features landed:

  • textDocument/definition - jump to symbol definition.
  • textDocument/references - find all usages.
  • textDocument/rename (+ prepare rename) - safe symbol rename.
  • textDocument/hover - quick symbol/type info.
  • textDocument/documentSymbol - file outline navigation.
  • textDocument/completion - entities, ports, and imports.
  • textDocument/semanticTokens/full - syntax-aware highlighting.
  • CodeLens (references, implementations) - inline code navigation counts.

This is the baseline for vscode-neva and future visual tooling over LSP transport.

🧪 Reliability & Quality

  • E2E infrastructure now handles timeout/cancel more safely by cleaning process groups to avoid orphan child processes.
  • Additional lint debt on main was cleaned while preparing this release baseline (staticcheck + wastedassign findings).
  • Runtime JSON spacing corruption fix (#1030) was a small but important bug-fix: pretty spacing no longer mutates string payload contents.

📑 Related PRs

Core to this release window:

  • #1051 Switch builtin maybe/error to tagged unions
  • #1049 fix(e2e): clean up orphan child processes on timeout/cancel
  • #1039 chore: migrate repo to Go 1.26 and add gofix CI check
  • #1038 Add streams.FromString and document converter policies
  • #1036 Add first-class bytes type and migrate binary APIs
  • #1035 Add dict<->stream converters in std
  • #1034 Add Go-parity scalar converters for builtin and strconv
  • #1031 Cleanup streams API and standardize stdlib port naming
  • #1030 Fix runtime JSON spacing corruption in message formatting
  • #1029 feat: require explicit aliases for top-level node declarations
  • #1026 chore: remove cmd/lsp after extraction to neva-lsp
  • #1025 Expose public APIs for external LSP extraction
  • #1024 Move LSP indexer to pkg/indexer
  • #1022 refactor: move ast and core packages to pkg
  • #1020 LSP: add core language features
  • #1018 refactor: remove deferred connections
  • #1013 Replace array-bypass => with [*]

Full Changelog: v0.34.0...v0.35.0