Nocter v0 Contract
This file is part of the Nocter language specification. The specification entry point is README.md.
This chapter defines the implementation contract for Nocter v0. It is narrower than the long-term language direction. A feature that is not listed here is not part of the v0 contract even if older notes or future-design sections mention it.
Interfaces And Excluded Features
Nocter v0 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:
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. 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 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 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 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 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 - 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. 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 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 source-level contract. v0 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, 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 typed facts, it must either be added to this contract or rejected before lowering.
Buildability Contract
build and run perform a v0 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 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 values in runtime positions, including payload enum
construction,
match, andif islowering - 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
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:
- 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 - payload-carrying enum values are typechecked in v0 but rejected before build/run lowering until their runtime layout is promoted into ABI v0
- fixed arrays use contiguous element layout with a compile-time length
- 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 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.