Nocter v0.2.0 Language Contract
This file is part of the Nocter language specification. The specification entry point is README.md.
This chapter records the implemented language contract released as Nocter v0.2.0. It is narrower than the long-term language direction. A feature that is not listed here is not part of the v0.2.0 release even if a later design chapter mentions it.
Interfaces And Excluded Features
Nocter v0.2.0 includes contract-only interface declarations and explicit interface conformance declarations.
An interface contains only public method signatures. It cannot contain method bodies, fields, associated data, default methods, associated types, or reusable code.
pub interface Printable {
pub method &self.print(): i32
}
impl Printable for User
Conformance is explicit and structural. impl Printable for User opts User into the contract, and typechecking verifies that User has public inherent methods matching each interface method after substituting Self with User. The compiler does not infer accidental conformance from a matching shape alone.
The following features are not part of v0.2.0:
traitdeclarations- embedding declarations such as
...Typeandpub ...Type - literal definitions such as
literal Vec<T> [...items: [T]]: Self - typed literal construction such as
Vec [1, 2, 3] - generalized
...spread, rest capture, and variadic capture forms - generic bounds such as
T: Interface - interface-bound method lookup
- dynamic dispatch and interface objects such as
dyn Printable whereclauses- class inheritance
- code reuse through interfaces
- user-defined primitive declarations outside the trusted standard-library boundary
trait is not a reserved keyword in v0.2.0. It is lexed as an identifier. Source forms that try to use trait syntax are diagnosed as removed syntax by the parser. interface is a reserved keyword.
Parser Contract
The v0.2.0 parser accepts these top-level item forms:
use pathas a module namespace import using the path's default nameuse path as nameas a module namespace import using an explicit nameuse path.nameuse path.{name_a, name_b}pub use path.namepub use path.{name_a, name_b}#target("target-name")immediately before one eligible top-level declarationprimitive name<T>(...): Typepub primitive name<T>(...): Typepub(nocter) primitive name<T>(...): Typefunc name<T>(...): Type { ... }pub func name<T>(...): Type { ... }pub(nocter) func name<T>(...): Type { ... }func Type.name<T>(...): Type { ... }pub func Type.name<T>(...): Type { ... }pub(nocter) func Type.name<T>(...): Type { ... }type Name<T> = Typepub type Name<T> = Typepub(nocter) type Name<T> = Typecopy struct Name<T> { ... }pub copy struct Name<T> { ... }pub(nocter) copy struct Name<T> { ... }struct Name<T> { ... }pub struct Name<T> { ... }pub(nocter) struct Name<T> { ... }enum Name<T> { ... }pub enum Name<T> { ... }pub(nocter) enum Name<T> { ... }interface Name<T> { pub method ... }pub interface Name<T> { pub method ... }pub(nocter) interface Name<T> { pub method ... }impl Type { method ...; drop ... }impl Interface for Typeimpl Interface for Type {}
Top-level use and pub use forms are accepted only at the start of a source file before non-use declarations.
#target("target-name") is a directive, not an attribute. It applies only to the single top-level declaration that immediately follows it. In v0.2.0 that following declaration must be a function, primitive, struct, enum, interface, or type alias. The parser recognizes the directive form everywhere so it can produce useful diagnostics, but target validation rejects #target outside the active Nocter home or before an ineligible declaration.
The v0.2.0 parser also accepts the non-pub use forms above at the start of any block scope. Block-scope use declarations are lexical declarations. They are not runtime statements, and their imported names are visible only inside the containing block after the declaration.
The v0.2.0 parser rejects these forms with diagnostics instead of accepting them as partial language support:
trait Name { ... }- wildcard imports such as
use std/io.*andpub use std/io.* - source-level prelude imports such as
use std/prelude - textual include such as
include std/prelude - dotted module paths such as
use std.io.print - explicit
.nctextensions in import paths such asuse ./config.nct.Config - relative or absolute path-like module expressions such as
./path/to/file.something()and/absolute/path/file.something() - top-level
useafter a non-usedeclaration - block-scope
useafter a non-usestatement or result expression - block-scope
pub use region name using allocator { ... }- legacy
matchfallback arms written aselse { ... } struct Name { ...Type }struct Name { pub ...Type }literal Type [...]func name(...values: [T]): Typeimpl Interface for Type { method ... }- generic bounds such as
<T: Interface> funcdeclarations insideimpl
The parser must not panic on malformed input. A parse failure must produce a diagnostic with a source span.
Resolver Contract
The resolver owns name binding for v0.2.0. Later stages, CLI diagnostics, and LSP features must use resolver output instead of reimplementing lookup.
The resolver must classify each referenced name as one of:
- resolved global symbol
- resolved local symbol
- unresolved identifier diagnostic
- unsupported deferred syntax diagnostic emitted earlier by the parser
The resolver's v0.2.0 symbol space includes:
- functions
- primitives
- imported namespaces
- imported symbols
- type aliases
- structs
- enums
- interfaces
- associated functions attached to nominal types
- inherent methods attached to nominal types
- inherent
dropmembers attached to nominal types - local parameters and bindings
Trait symbols are not part of the v0.2.0 source-level contract. v0.2.0 source can create interface symbols through the parser.
Typecheck Contract
The typechecker is the source of truth for typed facts used by backend lowering, CLI diagnostics, and LSP features.
For v0.2.0, typechecking must produce or diagnose:
- expression result types
- binding and parameter types
- readonly versus readwrite binding facts
- function, primitive, associated function, method, and drop signatures
- field access target and field type
- struct literal field checks
- enum variant construction and payload checks
- method call receiver type and selected inherent method
- explicit interface conformance against public inherent methods
- associated function call target
- move, copy, and drop state for owned values
- use-after-move and invalid explicit
dropdiagnostics - deferred source-form diagnostics that do not require target/runtime buildability facts
The backend must not infer language semantics that are missing from typechecking. If a source construct is not represented in the v0.2.0 typed facts, it must either be added to this contract or rejected before lowering.
Buildability Contract
build and run perform a v0.2.0 buildability validation step after typechecking and ownership checking, before IR or backend lowering. This step consumes typed facts and reports source diagnostics for constructs that are valid enough for check but not yet supported by v0.2.0 runtime lowering.
Rules:
- Buildability diagnostics are source diagnostics, not backend crashes.
- Buildability validation must run before target lowering, register allocation, code generation, or executable writing.
- The backend must receive only constructs that passed buildability validation.
- Buildability validation must resolve imported type aliases by their declaring source when classifying runtime-supported local values, field/member values, borrow arguments, slice element operations, and aggregate move/drop boundaries.
checkaccepts explicitly check-only constructs so tools can validate source syntax, imports, names, types, ownership, and diagnostics before runtime lowering is implemented.buildandrunmust reject check-only constructs instead of silently choosing placeholder runtime behavior.
Initial check-only or not-yet-buildable surfaces:
- payload-carrying enum move-only payload binding with unsupported
recursive drop trees and broader pattern target expressions;
payload-carrying enum construction, locals, returns, value arguments,
tag-only
if is Enum.variant(_)statements, tag-onlymatchstatements, and scalar, string/slice view, copy aggregate payload binding, and owned recursively droppable aggregate or supported move-only fixed-array payload binding over existing enum values and supported owned call-expression, constructor, and move-local pattern targets are buildable for the current payload subset; owned call expressions include plain calls, direct fallible calls handled by?,!, orcatch, and direct optional calls handled byotherwise, with active payload cleanup additionally supported for aggregate and fixed-array payload fields with supported recursive drop glue - fixed-array elements whose type tree contains a payload enum, until the array obligation model can represent the current element's nested partial state; payload enums nested in ordinary struct fields and other payload enums are buildable across the documented aggregate value boundaries
- string interpolation lowering until the explicit standard-library formatting construction path is complete
- ordinary input-dependent
errorsuccess-return helper calls outside directError.new(...)-style(&str, &str) -> errorfailure payload construction and input-free static payload wrappers; this includes helper parameters and method receivers std/process.env(name)useful runtime behavior until nested fallible/optional return lowering and process-context environment storage are promotedVec<T>storage paths outside the runtime-supported scalar,&str, and explicitly promoted copy-aggregate element subset- move-only fixed arrays outside the completed local, callable, and fully initialized struct-field storage/replacement lifecycle for supported recursively droppable struct elements; recursive field construction now tracks exiting struct and payload initializers, while field extraction moves remain deferred
Aggregate Type Contract
The aggregate contract covers structs, enums, optionals, fallible values, fixed arrays, and standard-library owned aggregate types such as String.
Frontend facts required by backend and ABI lowering:
- resolved nominal type identity
- field order and field types
- enum variant order and payload types
- fixed-array element type and compile-time length
copy structmarker- whether a type has an inherent
dropmember - expression ownership category: copy, move-only owned value, borrow, view, pointer, or unsized data behind indirection
- active move/drop state for each owned local place
Backend and ABI lowering may rely on these facts and must not repeat semantic lookup. Lowering may compute target layout and calling convention details from the typed facts.
Rules fixed for v0.2.0:
- struct fields are laid out in declaration order
- struct layout does not change when a
dropmember exists - payloadless enum values use the explicit
u8tag layout specified by ABI v0.2.0 - payload-carrying enum values use the tag-plus-payload-union layout specified by ABI v0.2.0
- payload-carrying enum construction, locals, returns, value arguments,
tag-only
if is Enum.variant(_)statements, and tag-onlymatchstatements may lower in the current payload subset over existing enum values and supported owned call-expression, constructor, and move-local pattern targets; scalar, string/slice view, copy aggregate payload binding, and owned recursively droppable aggregate or supported move-only fixed-array payload binding may lower inif isandmatchbranches over existing enum values and supported pattern targets; active payload cleanup may lower for supported recursive payload drop trees, including fixed-array elements in reverse index order and multi-field payload cleanup in reverse aggregate field order; variant-specific construction obligations track exiting payload initializers across locals, replacements, returns, and owned call arguments for the supported payload field shapes; move-only payload binding with unsupported recursive drop trees and broader pattern target expressions still reject before build/run - fixed arrays use contiguous element layout with a compile-time length
- fully initialized local fixed-array literals and whole-local literal replacements of supported recursively droppable struct elements may lower; their live elements are dropped in reverse index order at replacement, explicit drop, or scope exit
- local fixed-array literals, whole-local replacements, direct literal returns,
and direct value arguments may contain atomic fallible aggregate-call
elements handled by postfix
?; lowering tracks the completed element prefix, drops only that prefix on propagation, and publishes the complete array only after every element succeeds - call argument evaluation retains completed owned temporaries until the call begins, dropping earlier temporaries in reverse evaluation order if a later argument exits
- explicit moves transfer a supported move-only fixed array's cleanup
obligation between local bindings; moved or explicitly dropped
varlocals regain that obligation when reinitialized - supported move-only fixed arrays may cross function boundaries as direct or indirect returns, call-result bindings and replacements, value arguments, and owned parameters; plain, optional, and fallible calls transfer one cleanup obligation only after a successful result, while failure paths leave existing assignment targets live and never drop an uninitialized result slot
- fully initialized struct fields may store supported move-only fixed arrays; replacement stages the new array before recursively dropping the old field, and explicit moves from local arrays transfer their cleanup obligation into the field
- optional and fallible values use explicit tags
- values of 16 bytes or less use direct ABI classification
- values larger than 16 bytes use indirect ABI classification
- drop glue must drop only live fields, initialized array elements, or active payloads
- moved-from owned places are dead until assigned a new value
String Type Contract
String is an ordinary standard-library owned type, not a built-in type name. The compiler must not treat the identifier String as magic.
The compiler-owned string types are:
str: unsized UTF-8 byte data&str: borrowed UTF-8 slice withptr + lenABI
String literals have type &str, point at static storage, and do not allocate.
The v0.2.0 String contract for compiler and standard library integration:
Stringowns valid UTF-8 bytesStringis move-only unless the standard library exposes an explicit copy APIStringreleases its owned storage through its inherentdropmemberStringcan produce a borrowed&strview- constructing a
Stringfrom&stris explicit and fallible - interpolation, if parsed, must not lower until the standard-library construction API is implemented
The runtime representation is a standard-library ABI contract, not user-facing syntax. The initial implementation may use a pointer, length, and capacity representation, but source code must interact through ordinary associated functions and methods.
Tooling Contract
CLI diagnostics and LSP features must use the same parser, resolver, and typecheck facts as the compiler pipeline.
Required shared facts for tooling:
- source spans for declarations and references
- resolved symbol targets
- normalized type labels for identifiers and expressions
- function and method signature labels
- member and field targets
- diagnostic codes and source spans
LSP-specific code may translate these facts into protocol objects, but it must not introduce separate language lookup rules.