Release 1.0.0-beta.8 concrete type aliases

This commit is contained in:
sanjin 2026-05-22 18:55:33 +02:00
parent be6cdfb87c
commit 4f52a54bea
48 changed files with 2675 additions and 306 deletions

View File

@ -0,0 +1,60 @@
# 1.0.0-beta.8 Concrete Type Alias Foundation
Status: release scope for `1.0.0-beta.8`.
`1.0.0-beta.8` targets a deliberately small language-usability slice:
transparent top-level aliases for existing concrete Slovo types.
## Source Surface
The declaration form is:
```slo
(type Alias TargetType)
```
Examples:
```slo
(type JsonText string)
(type Scores (vec i32))
(type MaybeName (option string))
(type ReadResult (result string i32))
```
Aliases are module-local names. A later declaration in the same module may use
the alias wherever the resolved concrete target type is already supported.
## Resolution Contract
- Resolve aliases before typed-core lowering, checked import signatures,
backend layout, ABI decisions, and runtime behavior.
- Treat aliases as transparent names, not new nominal types.
- Preserve the target type's existing value-flow, constructor, operator,
`match`, field-access, import, and diagnostic behavior.
- Normalize exported function or struct signatures that mention local aliases
to their concrete target types for cross-module use.
- Generated documentation may show source alias spellings; runtime, checked
lowering, and cross-module typing must use the resolved concrete target.
- Reject failed alias resolution; do not silently fall back to a primitive type.
## Source Fixtures
- `lib/std/json.slo` uses local `JsonText` and `JsonField` aliases for already
encoded JSON fragments.
- `examples/projects/std-import-json/` uses the same alias shape in the
explicit standard-import fixture without importing aliases.
- `examples/projects/std-layout-local-json/` mirrors the facade and example as
a local source fixture.
These fixtures keep the existing JSON helper surface and test names. They do
not add compiler-known runtime calls or new public standard-library helper
names.
## Deferrals
This target does not add generic aliases, parameterized aliases, alias type
parameters, alias re-exports, cross-module alias imports, import aliases, glob
imports, maps/sets, alias-driven overloads, implicit casts, runtime helpers,
hosted runtime symbols, stable ABI/layout promises, or a standard-library API
freeze.

View File

@ -24,15 +24,18 @@ implementation scope.
networking (released in `1.0.0-beta.2` with read-only text file
handles plus narrow filesystem status and mutation calls).
3. Stabilize `lib/std` module boundaries and document beta-vs-stable APIs.
4. Improve language usability around entry points, `match`, aliases, and
concrete numeric completeness.
4. Improve language usability around entry points, `match`, concrete aliases,
and concrete numeric completeness.
5. Expand package/workspace discipline before remote registry work.
6. Add networking only after resource/error policy is coherent.
7. Add serialization/data-interchange helpers before richer network libraries
(released in `1.0.0-beta.7` with compact JSON text construction and JSON
string quoting).
8. Design generics and collection unification from real stdlib duplication
8. Promote concrete type aliases before generics so long concrete vector,
option/result, array, JSON, and resource-handle signatures can be named
without changing runtime representation (released in `1.0.0-beta.8`).
9. Design generics and collection unification from real stdlib duplication
pressure.
9. Add editor-facing diagnostics, watch mode, and generated documentation
10. Add editor-facing diagnostics, watch mode, and generated documentation
improvements.
10. Freeze migration/deprecation policy and cut stable only after beta feedback.
11. Freeze migration/deprecation policy and cut stable only after beta feedback.

View File

@ -0,0 +1,52 @@
# 1.0.0-beta.8 Release Review
Date: 2026-05-22
Scope: concrete type alias foundation.
## Verdict
The Slovo documentation and source-fixture side is coherent for a beta.8
concrete-alias slice. The contract is intentionally transparent: top-level
`(type Alias TargetType)` declarations name existing supported concrete target
types, normalize before backend/ABI decisions, and do not create runtime types,
hosted symbols, generic alias machinery, maps/sets, or cross-module alias
visibility.
## Review Notes
- The language spec defines syntax, resolution, formatter shape, diagnostics,
and explicit deferrals for concrete aliases.
- The public roadmap and stable-readiness notes place beta.8 between JSON data
interchange and generics/collection unification.
- `std/json.slo` uses `JsonText` and `JsonField` as local aliases only. The
helper export list remains unchanged.
- The explicit std-import and local JSON fixtures exercise local alias use
without importing, exporting, or re-exporting aliases.
- Glagol parser, checker, formatter, project import resolution, lowering
inspectors, diagnostics fixtures, and promotion inventory now carry the
matching implementation side.
- Reviewer findings were integrated: JSON fixtures use canonical alias spacing,
unsupported alias targets are rejected, alias/value name conflicts are
diagnosed, and alias export/import attempts produce explicit visibility
diagnostics.
## Verification
- `cargo fmt --check`
- `cargo test --test diagnostics_contract current_negative_cases_match_machine_diagnostic_snapshots`
- `cargo test --test concrete_type_aliases_beta`
- `cargo test --test project_mode type_aliases_are_local_across_project_visibility`
- `cargo test --test lowering_inspector`
- `cargo test --test promotion_gate promotion_gate_artifacts_are_aligned`
- `cargo test --test standard_json_source_facade_alpha`
- `cargo test`
- `git diff --check`
## Remaining Deferred Work
- Generated documentation may still display source alias spelling. Runtime
behavior, checked lowering, backend layout, and cross-module typing use the
resolved concrete target.
- Any future generic alias, cross-module alias import/export, or re-export
design.

View File

@ -6,7 +6,7 @@ This repository is the canonical public monorepo for the language design,
standard library source, compiler, runtime, examples, benchmarks, and technical
documents.
Current release: `1.0.0-beta.7`.
Current release: `1.0.0-beta.8`.
## Repository Layout
@ -24,20 +24,22 @@ scripts/ local release and document tooling
## Beta Scope
`1.0.0-beta.7` keeps the `1.0.0-beta` language baseline, includes the
`1.0.0-beta.8` keeps the `1.0.0-beta` language baseline, includes the
`1.0.0-beta.1` tooling/install hardening slice, the `1.0.0-beta.2`
runtime/resource foundation bundle, the `1.0.0-beta.3` standard-library
stabilization bundle, the `1.0.0-beta.4` language-usability diagnostics
bundle, the `1.0.0-beta.5` local package/workspace discipline bundle, and the
`1.0.0-beta.6` loopback networking foundation, plus the `1.0.0-beta.7`
serialization/data-interchange foundation. The language baseline supports
practical local command-line, file, and loopback-network programs with:
serialization/data-interchange foundation and the `1.0.0-beta.8` concrete type
alias foundation. The language baseline supports practical local command-line,
file, and loopback-network programs with:
- modules, explicit imports, packages, and local workspaces
- `new`, `check`, `fmt`, `test`, `doc`, and `build`
- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, and internal `unit`
- structs, enums, fixed arrays, concrete vectors, option/result families, and
current `match`
- module-local transparent concrete type aliases
- explicit `std/*.slo` imports from `lib/std`, installed `share/slovo/std`, or
`SLOVO_STD_PATH`
- beta-scoped loopback TCP handles through `std.net`
@ -48,6 +50,11 @@ Still deferred before stable: generics, maps/sets, broad package registry
semantics, DNS/TLS/async networking, LSP/watch/debug-adapter guarantees,
stable ABI and layout, and a stable standard-library compatibility freeze.
The next likely language slice is `1.0.0-beta.9`, focused on the first
generics and collection-unification design pressure. It should build on the
alias foundation without adding maps/sets or standard-library API freeze claims
until the contract and gates are explicit.
## Build And Test
```bash
@ -194,6 +201,21 @@ This is not a complete JSON library. Full parsing, recursive JSON values,
maps/sets, streaming encoders, schema validation, Unicode normalization, and a
stable data-interchange API freeze remain deferred.
## 1.0.0-beta.8 Concrete Type Alias Foundation
The `1.0.0-beta.8` release adds transparent concrete type aliases:
```slo
(type JsonText string)
```
Aliases are module-local names for already supported concrete target types.
They may appear in local signatures and annotations, but they do not create new
runtime representations or stable ABI names. Project imports of functions that
use aliases see the resolved concrete target type. Alias exports, imports,
re-exports, generic aliases, parameterized aliases, maps/sets, and new
compiler-known runtime names remain out of scope.
## Documentation
- [Language Manifest](docs/language/MANIFEST.md)

2
compiler/Cargo.lock generated
View File

@ -4,4 +4,4 @@ version = 3
[[package]]
name = "glagol"
version = "1.0.0-beta.7"
version = "1.0.0-beta.8"

View File

@ -1,6 +1,6 @@
[package]
name = "glagol"
version = "1.0.0-beta.7"
version = "1.0.0-beta.8"
edition = "2021"
description = "Glagol, the first compiler for the Slovo language"
license = "MIT OR Apache-2.0"

View File

@ -3,6 +3,7 @@ use crate::{token::Span, types::Type};
#[derive(Debug, Clone)]
pub struct Program {
pub module: String,
pub type_aliases: Vec<TypeAliasDecl>,
pub enums: Vec<EnumDecl>,
pub structs: Vec<StructDecl>,
pub c_imports: Vec<CImportDecl>,
@ -10,6 +11,15 @@ pub struct Program {
pub tests: Vec<Test>,
}
#[derive(Debug, Clone)]
pub struct TypeAliasDecl {
pub name: String,
pub name_span: Span,
pub target: Type,
pub target_span: Span,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct EnumDecl {
pub name: String,

View File

@ -1,9 +1,9 @@
use std::collections::{hash_map::Entry, HashMap, HashSet};
use std::collections::{hash_map::Entry, BTreeSet, HashMap, HashSet};
use crate::{
ast::{
BinaryOp, EnumDecl, Expr, ExprKind, Function, MatchArm, MatchPatternKind, Program,
StructDecl, StructInitField, Test,
StructDecl, StructInitField, Test, TypeAliasDecl,
},
diag::Diagnostic,
lower, std_runtime,
@ -294,13 +294,23 @@ pub fn check_program_with_imports(
fn check_program_inner(
file: &str,
program: Program,
mut program: Program,
external_functions: &[ExternalFunction],
external_structs: &[ExternalStruct],
external_enums: &[ExternalEnum],
project_resolution: bool,
) -> Result<CheckedProgram, Vec<Diagnostic>> {
let mut errors = Vec::new();
errors.extend(resolve_type_aliases(
file,
&mut program,
external_structs,
external_enums,
));
if !errors.is_empty() {
return Err(errors);
}
let mut functions = HashMap::new();
let mut structs = HashMap::new();
let mut enums = HashMap::new();
@ -580,6 +590,641 @@ fn is_reserved_callable_name(name: &str) -> bool {
std_runtime::is_reserved_name(name) || unsafe_ops::is_reserved_head(name)
}
fn resolve_type_aliases(
file: &str,
program: &mut Program,
external_structs: &[ExternalStruct],
external_enums: &[ExternalEnum],
) -> Vec<Diagnostic> {
if program.type_aliases.is_empty() {
return Vec::new();
}
let mut errors = Vec::new();
let mut struct_names = HashMap::new();
for struct_decl in &program.structs {
struct_names
.entry(struct_decl.name.clone())
.or_insert(struct_decl.name_span);
}
for struct_decl in external_structs {
struct_names
.entry(struct_decl.name.clone())
.or_insert(struct_decl.span);
}
let mut enum_names = HashMap::new();
for enum_decl in &program.enums {
enum_names
.entry(enum_decl.name.clone())
.or_insert(enum_decl.name_span);
}
for enum_decl in external_enums {
enum_names
.entry(enum_decl.name.clone())
.or_insert(enum_decl.span);
}
let mut function_names = HashMap::new();
for function in &program.functions {
function_names
.entry(function.name.clone())
.or_insert(function.span);
}
let mut c_import_names = HashMap::new();
for import in &program.c_imports {
c_import_names
.entry(import.name.clone())
.or_insert(import.name_span);
}
let mut aliases = HashMap::<String, &TypeAliasDecl>::new();
for alias in &program.type_aliases {
match aliases.entry(alias.name.clone()) {
Entry::Vacant(entry) => {
entry.insert(alias);
}
Entry::Occupied(entry) => errors.push(
Diagnostic::new(
file,
"DuplicateTypeAlias",
format!("duplicate type alias `{}`", alias.name),
)
.with_span(alias.name_span)
.related("original type alias", entry.get().name_span),
),
}
}
for alias in &program.type_aliases {
if is_reserved_type_name(&alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!(
"type alias `{}` conflicts with a built-in type name",
alias.name
),
)
.with_span(alias.name_span)
.hint("choose an alias name distinct from built-in type names"),
);
}
if let Some(span) = struct_names.get(&alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!("type alias `{}` conflicts with a struct", alias.name),
)
.with_span(alias.name_span)
.related("struct declaration", *span),
);
}
if let Some(span) = enum_names.get(&alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!("type alias `{}` conflicts with an enum", alias.name),
)
.with_span(alias.name_span)
.related("enum declaration", *span),
);
}
if let Some(span) = function_names.get(&alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!("type alias `{}` conflicts with a function", alias.name),
)
.with_span(alias.name_span)
.related("function declaration", *span),
);
}
if let Some(span) = c_import_names.get(&alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!("type alias `{}` conflicts with a C import", alias.name),
)
.with_span(alias.name_span)
.related("C import declaration", *span),
);
}
if matches!(&alias.target, Type::Named(name) if name == &alias.name) {
errors.push(
Diagnostic::new(
file,
"SelfTypeAlias",
format!("type alias `{}` directly aliases itself", alias.name),
)
.with_span(alias.target_span)
.hint("point the alias at an existing concrete type"),
);
}
}
if !errors.is_empty() {
return errors;
}
let known_structs = struct_names.keys().cloned().collect::<HashSet<_>>();
let known_enums = enum_names.keys().cloned().collect::<HashSet<_>>();
let mut resolved = HashMap::new();
let mut failed = HashSet::new();
let mut reported_cycles = BTreeSet::new();
let mut names = aliases.keys().cloned().collect::<Vec<_>>();
names.sort();
for name in names {
resolve_alias_target(
file,
&name,
&aliases,
&known_structs,
&known_enums,
&mut resolved,
&mut failed,
&mut Vec::new(),
&mut reported_cycles,
&mut errors,
);
}
if !errors.is_empty() {
return errors;
}
rewrite_program_alias_types(program, &resolved);
Vec::new()
}
fn resolve_alias_target(
file: &str,
name: &str,
aliases: &HashMap<String, &TypeAliasDecl>,
structs: &HashSet<String>,
enums: &HashSet<String>,
resolved: &mut HashMap<String, Type>,
failed: &mut HashSet<String>,
stack: &mut Vec<String>,
reported_cycles: &mut BTreeSet<String>,
errors: &mut Vec<Diagnostic>,
) -> Option<Type> {
if let Some(ty) = resolved.get(name) {
return Some(ty.clone());
}
if failed.contains(name) {
return None;
}
if let Some(cycle_start) = stack.iter().position(|candidate| candidate == name) {
let cycle = stack[cycle_start..].to_vec();
let mut key = cycle.clone();
key.sort();
if reported_cycles.insert(key.join("|")) {
errors.push(type_alias_cycle(file, &cycle, aliases));
}
failed.extend(cycle);
return None;
}
let alias = aliases.get(name).copied()?;
stack.push(name.to_string());
let target = resolve_alias_target_type(
file,
&alias.target,
alias.target_span,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
);
stack.pop();
if let Some(target) = target {
resolved.insert(name.to_string(), target.clone());
Some(target)
} else {
failed.insert(name.to_string());
None
}
}
fn resolve_alias_target_type(
file: &str,
ty: &Type,
span: Span,
aliases: &HashMap<String, &TypeAliasDecl>,
structs: &HashSet<String>,
enums: &HashSet<String>,
resolved: &mut HashMap<String, Type>,
failed: &mut HashSet<String>,
stack: &mut Vec<String>,
reported_cycles: &mut BTreeSet<String>,
errors: &mut Vec<Diagnostic>,
) -> Option<Type> {
match ty {
Type::Named(name) if aliases.contains_key(name) => resolve_alias_target(
file,
name,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
),
Type::Named(name) if structs.contains(name) || enums.contains(name) => {
Some(Type::Named(name.clone()))
}
Type::Named(name) => {
errors.push(
Diagnostic::new(
file,
"UnknownTypeAliasTarget",
format!("type alias target `{}` is not a known concrete type", name),
)
.with_span(span)
.expected("built-in type, known struct, known enum, or known type alias")
.found(name.clone())
.hint("declare the target type or alias before checking this alias set"),
);
None
}
Type::Array(inner, len) => {
let resolved_inner = resolve_alias_target_type(
file,
inner,
span,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
)?;
let target = Type::Array(Box::new(resolved_inner), *len);
if alias_target_type_supported(&target, structs, enums) {
Some(target)
} else {
errors.push(unsupported_type_alias_target(file, span, &target));
None
}
}
Type::Vec(inner) => {
let resolved_inner = resolve_alias_target_type(
file,
inner,
span,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
)?;
let target = Type::Vec(Box::new(resolved_inner));
if alias_target_type_supported(&target, structs, enums) {
Some(target)
} else {
errors.push(unsupported_type_alias_target(file, span, &target));
None
}
}
Type::Option(inner) => {
let resolved_inner = resolve_alias_target_type(
file,
inner,
span,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
)?;
let target = Type::Option(Box::new(resolved_inner));
if alias_target_type_supported(&target, structs, enums) {
Some(target)
} else {
errors.push(unsupported_type_alias_target(file, span, &target));
None
}
}
Type::Result(ok, err) => {
let resolved_ok = resolve_alias_target_type(
file,
ok,
span,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
)?;
let resolved_err = resolve_alias_target_type(
file,
err,
span,
aliases,
structs,
enums,
resolved,
failed,
stack,
reported_cycles,
errors,
)?;
let target = Type::Result(Box::new(resolved_ok), Box::new(resolved_err));
if alias_target_type_supported(&target, structs, enums) {
Some(target)
} else {
errors.push(unsupported_type_alias_target(file, span, &target));
None
}
}
_ => {
if alias_target_type_supported(ty, structs, enums) {
Some(ty.clone())
} else {
errors.push(unsupported_type_alias_target(file, span, ty));
None
}
}
}
}
fn alias_target_type_supported(
ty: &Type,
structs: &HashSet<String>,
enums: &HashSet<String>,
) -> bool {
match ty {
Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String => {
true
}
Type::Named(name) => structs.contains(name) || enums.contains(name),
Type::Array(inner, len) => {
*len > 0
&& (matches!(
&**inner,
Type::I32
| Type::I64
| Type::U32
| Type::U64
| Type::F64
| Type::Bool
| Type::String
) || matches!(&**inner, Type::Named(name) if structs.contains(name) || enums.contains(name)))
}
Type::Vec(inner) => matches!(
&**inner,
Type::I32 | Type::I64 | Type::F64 | Type::Bool | Type::String
),
Type::Option(inner) => option_payload_type_supported(inner),
Type::Result(ok, err) => result_payload_types_supported(ok, err),
Type::Unit | Type::Ptr(_) | Type::Slice(_) => false,
}
}
fn unsupported_type_alias_target(file: &str, span: Span, ty: &Type) -> Diagnostic {
Diagnostic::new(
file,
"UnsupportedTypeAliasTarget",
"type alias target type is not supported in beta.8",
)
.with_span(span)
.expected("i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
.found(ty.to_string())
.hint("aliases are transparent and may only target concrete types already supported in the target use positions")
}
fn type_alias_cycle(
file: &str,
cycle: &[String],
aliases: &HashMap<String, &TypeAliasDecl>,
) -> Diagnostic {
let first = &cycle[0];
let first_alias = aliases[first];
let mut diag = Diagnostic::new(
file,
"TypeAliasCycle",
format!("type alias cycle includes `{}`", first),
)
.with_span(first_alias.name_span)
.hint("type aliases must resolve to an existing non-alias concrete type");
for name in &cycle[1..] {
diag = diag.related(
format!("cycle also includes `{}`", name),
aliases[name].name_span,
);
}
diag
}
fn rewrite_program_alias_types(program: &mut Program, aliases: &HashMap<String, Type>) {
for enum_decl in &mut program.enums {
for variant in &mut enum_decl.variants {
if let Some(payload_ty) = &mut variant.payload_ty {
*payload_ty = resolve_use_type(payload_ty, aliases);
}
}
}
for struct_decl in &mut program.structs {
for field in &mut struct_decl.fields {
field.ty = resolve_use_type(&field.ty, aliases);
}
}
for import in &mut program.c_imports {
for param in &mut import.params {
param.ty = resolve_use_type(&param.ty, aliases);
}
import.return_type = resolve_use_type(&import.return_type, aliases);
}
for function in &mut program.functions {
for param in &mut function.params {
param.ty = resolve_use_type(&param.ty, aliases);
}
function.return_type = resolve_use_type(&function.return_type, aliases);
for expr in &mut function.body {
rewrite_expr_alias_types(expr, aliases);
}
}
for test in &mut program.tests {
for expr in &mut test.body {
rewrite_expr_alias_types(expr, aliases);
}
}
}
fn rewrite_expr_alias_types(expr: &mut Expr, aliases: &HashMap<String, Type>) {
match &mut expr.kind {
ExprKind::StructInit { fields, .. } => {
for field in fields {
rewrite_expr_alias_types(&mut field.expr, aliases);
}
}
ExprKind::ArrayInit {
elem_ty, elements, ..
} => {
*elem_ty = resolve_use_type(elem_ty, aliases);
for element in elements {
rewrite_expr_alias_types(element, aliases);
}
}
ExprKind::OptionSome {
payload_ty, value, ..
} => {
*payload_ty = resolve_use_type(payload_ty, aliases);
rewrite_expr_alias_types(value, aliases);
}
ExprKind::OptionNone { payload_ty, .. } => {
*payload_ty = resolve_use_type(payload_ty, aliases);
}
ExprKind::ResultOk {
ok_ty,
err_ty,
value,
..
}
| ExprKind::ResultErr {
ok_ty,
err_ty,
value,
..
} => {
*ok_ty = resolve_use_type(ok_ty, aliases);
*err_ty = resolve_use_type(err_ty, aliases);
rewrite_expr_alias_types(value, aliases);
}
ExprKind::OptionIsSome { value }
| ExprKind::OptionIsNone { value }
| ExprKind::OptionUnwrapSome { value }
| ExprKind::ResultIsOk { value, .. }
| ExprKind::ResultIsErr { value, .. }
| ExprKind::ResultUnwrapOk { value, .. }
| ExprKind::ResultUnwrapErr { value, .. } => rewrite_expr_alias_types(value, aliases),
ExprKind::EnumVariant { args, .. } | ExprKind::Call { args, .. } => {
for arg in args {
rewrite_expr_alias_types(arg, aliases);
}
}
ExprKind::FieldAccess { value, .. } => rewrite_expr_alias_types(value, aliases),
ExprKind::Index { array, index } => {
rewrite_expr_alias_types(array, aliases);
rewrite_expr_alias_types(index, aliases);
}
ExprKind::Local { ty, expr, .. } => {
*ty = resolve_use_type(ty, aliases);
rewrite_expr_alias_types(expr, aliases);
}
ExprKind::Set { expr, .. } => rewrite_expr_alias_types(expr, aliases),
ExprKind::Binary { left, right, .. } => {
rewrite_expr_alias_types(left, aliases);
rewrite_expr_alias_types(right, aliases);
}
ExprKind::If {
condition,
then_expr,
else_expr,
} => {
rewrite_expr_alias_types(condition, aliases);
rewrite_expr_alias_types(then_expr, aliases);
rewrite_expr_alias_types(else_expr, aliases);
}
ExprKind::Match { subject, arms } => {
rewrite_expr_alias_types(subject, aliases);
for arm in arms {
for expr in &mut arm.body {
rewrite_expr_alias_types(expr, aliases);
}
}
}
ExprKind::While { condition, body } => {
rewrite_expr_alias_types(condition, aliases);
for expr in body {
rewrite_expr_alias_types(expr, aliases);
}
}
ExprKind::Unsafe { body } => {
for expr in body {
rewrite_expr_alias_types(expr, aliases);
}
}
ExprKind::Int(_)
| ExprKind::Int64(_)
| ExprKind::UInt32(_)
| ExprKind::UInt64(_)
| ExprKind::Float(_)
| ExprKind::Bool(_)
| ExprKind::String(_)
| ExprKind::Var(_) => {}
}
}
fn resolve_use_type(ty: &Type, aliases: &HashMap<String, Type>) -> Type {
match ty {
Type::Named(name) => aliases
.get(name)
.cloned()
.unwrap_or_else(|| Type::Named(name.clone())),
Type::Ptr(inner) => Type::Ptr(Box::new(resolve_use_type(inner, aliases))),
Type::Array(inner, len) => Type::Array(Box::new(resolve_use_type(inner, aliases)), *len),
Type::Vec(inner) => Type::Vec(Box::new(resolve_use_type(inner, aliases))),
Type::Slice(inner) => Type::Slice(Box::new(resolve_use_type(inner, aliases))),
Type::Option(inner) => Type::Option(Box::new(resolve_use_type(inner, aliases))),
Type::Result(ok, err) => Type::Result(
Box::new(resolve_use_type(ok, aliases)),
Box::new(resolve_use_type(err, aliases)),
),
_ => ty.clone(),
}
}
fn is_reserved_type_name(name: &str) -> bool {
matches!(
name,
"i32"
| "i64"
| "u32"
| "u64"
| "f64"
| "bool"
| "unit"
| "string"
| "ptr"
| "slice"
| "option"
| "result"
| "array"
| "vec"
)
}
fn check_struct_decl(
file: &str,
struct_decl: &StructDecl,

View File

@ -19,6 +19,7 @@ pub fn format(file: &str, source: &str, forms: &[SExpr]) -> Result<String, Vec<D
function_names: collect_function_names(forms),
struct_names: collect_struct_names(forms),
enum_names: collect_enum_names(forms),
type_alias_names: collect_type_alias_names(forms),
errors: comment_errors
.into_iter()
.map(|comment| unsupported_non_full_line_comment(file, comment.span))
@ -42,6 +43,7 @@ struct Formatter<'a> {
function_names: HashSet<String>,
struct_names: HashSet<String>,
enum_names: HashSet<String>,
type_alias_names: HashSet<String>,
errors: Vec<Diagnostic>,
}
@ -64,6 +66,7 @@ impl Formatter<'_> {
Some("module") => self.write_module(form),
Some("import") => self.write_import(form),
Some("import_c") => self.write_c_import(form),
Some("type") => self.write_type_alias(form),
Some("enum") => self.write_enum(form),
Some("struct") => self.write_struct(form),
Some("fn") => self.write_function(form),
@ -251,6 +254,59 @@ impl Formatter<'_> {
);
}
fn write_type_alias(&mut self, form: &SExpr) {
let Some(items) = expect_list(form) else {
self.errors.push(
Diagnostic::new(
self.file,
"MalformedTypeAlias",
"type alias form must be a list",
)
.with_span(form.span),
);
return;
};
if items.len() != 3 {
self.errors.push(
Diagnostic::new(
self.file,
"MalformedTypeAlias",
"type alias form must be `(type Alias TargetType)`",
)
.with_span(form.span),
);
return;
}
let Some(name) = expect_ident(&items[1]) else {
self.errors.push(
Diagnostic::new(
self.file,
"InvalidTypeAliasName",
"type alias name must be an identifier",
)
.with_span(items[1].span),
);
return;
};
let Some(target) = self.render_alias_target_type(&items[2]) else {
return;
};
self.reject_comments_before(
form.span.end,
"formatter does not support comments inside type alias forms",
);
self.output.push_str("(type ");
self.output.push_str(name);
self.output.push(' ');
self.output.push_str(&target);
self.output.push(')');
}
fn write_struct(&mut self, form: &SExpr) {
let Some(items) = expect_list(form) else {
self.errors.push(
@ -512,17 +568,17 @@ impl Formatter<'_> {
} else if is_ident(&variant_items[1], "string") {
"string".to_string()
} else if let Some(name) = expect_ident(&variant_items[1]) {
if self.struct_names.contains(name) {
if self.struct_names.contains(name) || self.type_alias_names.contains(name) {
name.to_string()
} else {
self.errors.push(
Diagnostic::new(
self.file,
"UnsupportedFormatterForm",
"formatter supports only unary direct i32, i64, f64, bool, string, and known non-recursive struct enum payload variants",
"formatter supports only unary direct i32, i64, f64, bool, string, known type aliases, and known non-recursive struct enum payload variants",
)
.with_span(variant_items[1].span)
.expected("i32, i64, f64, bool, string, or known non-recursive struct type"),
.expected("i32, i64, f64, bool, string, known type alias, or known non-recursive struct type"),
);
continue;
}
@ -531,10 +587,10 @@ impl Formatter<'_> {
Diagnostic::new(
self.file,
"UnsupportedFormatterForm",
"formatter supports only unary direct i32, i64, f64, bool, string, and known non-recursive struct enum payload variants",
"formatter supports only unary direct i32, i64, f64, bool, string, known type aliases, and known non-recursive struct enum payload variants",
)
.with_span(variant_items[1].span)
.expected("i32, i64, f64, bool, string, or known non-recursive struct type"),
.expected("i32, i64, f64, bool, string, known type alias, or known non-recursive struct type"),
);
continue;
};
@ -847,7 +903,10 @@ impl Formatter<'_> {
} else if is_ident(&pair[1], "string") {
"string".to_string()
} else if let Some(name) = expect_ident(&pair[1]) {
if self.struct_names.contains(name) || self.enum_names.contains(name) {
if self.struct_names.contains(name)
|| self.enum_names.contains(name)
|| self.type_alias_names.contains(name)
{
name.to_string()
} else {
self.errors.push(
@ -861,14 +920,18 @@ impl Formatter<'_> {
continue;
}
} else if let Some(items) = expect_list(&pair[1]) {
if let Some(text) = render_option_result_type(items) {
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
text
} else if let Some(text) =
render_supported_array_type(items, &self.struct_names, &self.enum_names)
} else if let Some(text) = render_supported_array_type(
items,
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) {
text
} else if let Some(text) = render_supported_vec_type(items, &self.type_alias_names)
{
text
} else if let Some(text) = render_supported_vec_type(items) {
text
} else {
self.errors.push(
Diagnostic::new(
@ -946,7 +1009,10 @@ impl Formatter<'_> {
}
if let Some(name) = expect_ident(form) {
if self.struct_names.contains(name) || self.enum_names.contains(name) {
if self.struct_names.contains(name)
|| self.enum_names.contains(name)
|| self.type_alias_names.contains(name)
{
return Some(name.to_string());
}
}
@ -963,16 +1029,20 @@ impl Formatter<'_> {
return None;
};
if let Some(text) = render_option_result_type(items) {
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
return Some(text);
}
if let Some(text) = render_supported_array_type(items, &self.struct_names, &self.enum_names)
{
if let Some(text) = render_supported_array_type(
items,
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) {
return Some(text);
}
if let Some(text) = render_supported_vec_type(items) {
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
return Some(text);
}
@ -987,6 +1057,58 @@ impl Formatter<'_> {
None
}
fn render_alias_target_type(&mut self, form: &SExpr) -> Option<String> {
if let Some(text) = render_direct_type_atom(
form,
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) {
return Some(text);
}
let Some(items) = expect_list(form) else {
self.errors.push(
Diagnostic::new(
self.file,
"InvalidTypeAliasTarget",
"type alias target type is invalid",
)
.with_span(form.span)
.expected("concrete type"),
);
return None;
};
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
return Some(text);
}
if let Some(text) = render_supported_array_type(
items,
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) {
return Some(text);
}
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
return Some(text);
}
self.errors.push(
Diagnostic::new(
self.file,
"InvalidTypeAliasTarget",
"type alias target type is invalid",
)
.with_span(form.span)
.expected("concrete type"),
);
None
}
fn render_c_import_return_type(&mut self, form: &SExpr) -> Option<String> {
if is_ident(form, "i32") {
return Some("i32".to_string());
@ -1318,6 +1440,12 @@ impl Formatter<'_> {
is_array: false,
});
}
if self.type_alias_names.contains(name) {
return Some(RenderedType {
text: name.to_string(),
is_array: false,
});
}
}
let Some(items) = expect_list(form) else {
@ -1332,14 +1460,14 @@ impl Formatter<'_> {
return None;
};
if let Some(text) = render_option_result_type(items) {
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
return Some(RenderedType {
text,
is_array: false,
});
}
if let Some(text) = render_supported_vec_type(items) {
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
return Some(RenderedType {
text,
is_array: false,
@ -1362,6 +1490,7 @@ impl Formatter<'_> {
&items[1],
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) else {
self.errors.push(
Diagnostic::new(
@ -1422,7 +1551,10 @@ impl Formatter<'_> {
}
if let Some(name) = expect_ident(form) {
if self.struct_names.contains(name) || self.enum_names.contains(name) {
if self.struct_names.contains(name)
|| self.enum_names.contains(name)
|| self.type_alias_names.contains(name)
{
return Some(name.to_string());
}
}
@ -1439,16 +1571,20 @@ impl Formatter<'_> {
return None;
};
if let Some(text) = render_option_result_type(items) {
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
return Some(text);
}
if let Some(text) = render_supported_array_type(items, &self.struct_names, &self.enum_names)
{
if let Some(text) = render_supported_array_type(
items,
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) {
return Some(text);
}
if let Some(text) = render_supported_vec_type(items) {
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
return Some(text);
}
@ -2051,6 +2187,7 @@ impl Formatter<'_> {
&items[1],
&self.struct_names,
&self.enum_names,
&self.type_alias_names,
) else {
self.errors.push(
Diagnostic::new(
@ -2113,21 +2250,7 @@ impl Formatter<'_> {
return None;
}
let payload_type = if is_ident(&items[1], "i32") {
"i32"
} else if is_ident(&items[1], "i64") {
"i64"
} else if is_ident(&items[1], "u32") {
"u32"
} else if is_ident(&items[1], "u64") {
"u64"
} else if is_ident(&items[1], "f64") {
"f64"
} else if is_ident(&items[1], "bool") {
"bool"
} else if is_ident(&items[1], "string") {
"string"
} else {
let Some(payload_type) = render_payload_type_atom(&items[1], &self.type_alias_names) else {
self.errors.push(
Diagnostic::new(
self.file,
@ -2169,21 +2292,18 @@ impl Formatter<'_> {
return None;
}
let result_type = if is_ident(&items[1], "i32") && is_ident(&items[2], "i32") {
"i32 i32"
} else if is_ident(&items[1], "i64") && is_ident(&items[2], "i32") {
"i64 i32"
} else if is_ident(&items[1], "u32") && is_ident(&items[2], "i32") {
"u32 i32"
} else if is_ident(&items[1], "u64") && is_ident(&items[2], "i32") {
"u64 i32"
} else if is_ident(&items[1], "f64") && is_ident(&items[2], "i32") {
"f64 i32"
} else if is_ident(&items[1], "bool") && is_ident(&items[2], "i32") {
"bool i32"
} else if is_ident(&items[1], "string") && is_ident(&items[2], "i32") {
"string i32"
} else {
let Some(ok_ty) = render_payload_type_atom(&items[1], &self.type_alias_names) else {
self.errors.push(
Diagnostic::new(
self.file,
"UnsupportedFormatterForm",
"formatter supports only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)` constructors",
)
.with_span(span),
);
return None;
};
let Some(err_ty) = render_result_err_type_atom(&items[2], &self.type_alias_names) else {
self.errors.push(
Diagnostic::new(
self.file,
@ -2196,7 +2316,7 @@ impl Formatter<'_> {
};
let value = self.render_expr(&items[3], env)?;
Some(format!("({} {} {})", name, result_type, value))
Some(format!("({} {} {} {})", name, ok_ty, err_ty, value))
}
fn render_call(
@ -2355,6 +2475,19 @@ fn collect_enum_names(forms: &[SExpr]) -> HashSet<String> {
.collect()
}
fn collect_type_alias_names(forms: &[SExpr]) -> HashSet<String> {
forms
.iter()
.filter_map(|form| {
let items = expect_list(form)?;
if !is_ident(items.first()?, "type") {
return None;
}
expect_ident(items.get(1)?).map(str::to_string)
})
.collect()
}
fn qualified_enum_name(name: &str) -> Option<(&str, &str)> {
let (enum_name, variant) = name.split_once('.')?;
if enum_name.is_empty() || variant.is_empty() || variant.contains('.') {
@ -2467,89 +2600,19 @@ fn list_head(form: &SExpr) -> Option<&str> {
expect_ident(first)
}
fn render_option_result_type(items: &[SExpr]) -> Option<String> {
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "i32") {
return Some("(option i32)".to_string());
fn render_option_result_type(
items: &[SExpr],
type_alias_names: &HashSet<String>,
) -> Option<String> {
if items.len() == 2 && is_ident(&items[0], "option") {
let payload = render_payload_type_atom(&items[1], type_alias_names)?;
return Some(format!("(option {})", payload));
}
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "i64") {
return Some("(option i64)".to_string());
}
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "u32") {
return Some("(option u32)".to_string());
}
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "u64") {
return Some("(option u64)".to_string());
}
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "f64") {
return Some("(option f64)".to_string());
}
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "bool") {
return Some("(option bool)".to_string());
}
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "string") {
return Some("(option string)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "i32")
&& is_ident(&items[2], "i32")
{
return Some("(result i32 i32)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "i64")
&& is_ident(&items[2], "i32")
{
return Some("(result i64 i32)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "u32")
&& is_ident(&items[2], "i32")
{
return Some("(result u32 i32)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "u64")
&& is_ident(&items[2], "i32")
{
return Some("(result u64 i32)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "f64")
&& is_ident(&items[2], "i32")
{
return Some("(result f64 i32)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "bool")
&& is_ident(&items[2], "i32")
{
return Some("(result bool i32)".to_string());
}
if items.len() == 3
&& is_ident(&items[0], "result")
&& is_ident(&items[1], "string")
&& is_ident(&items[2], "i32")
{
return Some("(result string i32)".to_string());
if items.len() == 3 && is_ident(&items[0], "result") {
let ok = render_payload_type_atom(&items[1], type_alias_names)?;
let err = render_result_err_type_atom(&items[2], type_alias_names)?;
return Some(format!("(result {} {})", ok, err));
}
None
@ -2559,42 +2622,27 @@ fn render_supported_array_constructor_type(
form: &SExpr,
struct_names: &HashSet<String>,
enum_names: &HashSet<String>,
type_alias_names: &HashSet<String>,
) -> Option<String> {
if is_ident(form, "i32") {
Some("i32".to_string())
} else if is_ident(form, "i64") {
Some("i64".to_string())
} else if is_ident(form, "u32") {
Some("u32".to_string())
} else if is_ident(form, "u64") {
Some("u64".to_string())
} else if is_ident(form, "f64") {
Some("f64".to_string())
} else if is_ident(form, "bool") {
Some("bool".to_string())
} else if is_ident(form, "string") {
Some("string".to_string())
} else if let Some(name) = expect_ident(form) {
if struct_names.contains(name) || enum_names.contains(name) {
Some(name.to_string())
} else {
None
}
} else {
None
}
render_direct_type_atom(form, struct_names, enum_names, type_alias_names)
}
fn render_supported_array_type(
items: &[SExpr],
struct_names: &HashSet<String>,
enum_names: &HashSet<String>,
type_alias_names: &HashSet<String>,
) -> Option<String> {
if items.len() != 3 || !is_ident(&items[0], "array") {
return None;
}
let elem_ty = render_supported_array_constructor_type(&items[1], struct_names, enum_names)?;
let elem_ty = render_supported_array_constructor_type(
&items[1],
struct_names,
enum_names,
type_alias_names,
)?;
let len = expect_int(&items[2])?;
if len <= 0 {
return None;
@ -2603,7 +2651,10 @@ fn render_supported_array_type(
Some(format!("(array {} {})", elem_ty, len))
}
fn render_supported_vec_type(items: &[SExpr]) -> Option<String> {
fn render_supported_vec_type(
items: &[SExpr],
type_alias_names: &HashSet<String>,
) -> Option<String> {
if items.len() == 2 && is_ident(&items[0], "vec") {
if is_ident(&items[1], "i32") {
return Some("(vec i32)".to_string());
@ -2620,11 +2671,66 @@ fn render_supported_vec_type(items: &[SExpr]) -> Option<String> {
if is_ident(&items[1], "string") {
return Some("(vec string)".to_string());
}
if let Some(name) = expect_ident(&items[1]) {
if type_alias_names.contains(name) {
return Some(format!("(vec {})", name));
}
}
}
None
}
fn render_payload_type_atom(form: &SExpr, type_alias_names: &HashSet<String>) -> Option<String> {
if is_ident(form, "i32") {
Some("i32".to_string())
} else if is_ident(form, "i64") {
Some("i64".to_string())
} else if is_ident(form, "u32") {
Some("u32".to_string())
} else if is_ident(form, "u64") {
Some("u64".to_string())
} else if is_ident(form, "f64") {
Some("f64".to_string())
} else if is_ident(form, "bool") {
Some("bool".to_string())
} else if is_ident(form, "string") {
Some("string".to_string())
} else if let Some(name) = expect_ident(form) {
type_alias_names.contains(name).then(|| name.to_string())
} else {
None
}
}
fn render_result_err_type_atom(form: &SExpr, type_alias_names: &HashSet<String>) -> Option<String> {
if is_ident(form, "i32") {
Some("i32".to_string())
} else if let Some(name) = expect_ident(form) {
type_alias_names.contains(name).then(|| name.to_string())
} else {
None
}
}
fn render_direct_type_atom(
form: &SExpr,
struct_names: &HashSet<String>,
enum_names: &HashSet<String>,
type_alias_names: &HashSet<String>,
) -> Option<String> {
if let Some(text) = render_payload_type_atom(form, type_alias_names) {
return Some(text);
}
let name = expect_ident(form)?;
if struct_names.contains(name) || enum_names.contains(name) {
Some(name.to_string())
} else {
None
}
}
fn expect_list(form: &SExpr) -> Option<&[SExpr]> {
match &form.kind {
SExprKind::List(items) => Some(items),

View File

@ -5,7 +5,7 @@ use crate::{
ast::{
BinaryOp, CImportDecl, EnumDecl, EnumVariantDecl, Expr, ExprKind, Function, MatchArm,
MatchPattern, MatchPatternKind, Param, Program, StructDecl, StructField, StructInitField,
Test,
Test, TypeAliasDecl,
},
diag::Diagnostic,
sexpr::{Atom, SExpr, SExprKind},
@ -23,6 +23,8 @@ pub fn lower_program_with_imported_names(
imported_names: &[String],
) -> Result<Program, Vec<Diagnostic>> {
let mut module = None;
let mut type_aliases = Vec::new();
let mut alias_names = HashMap::new();
let mut enums = Vec::new();
let mut enum_names = imported_names.iter().cloned().collect::<HashSet<_>>();
let mut structs = Vec::new();
@ -49,10 +51,67 @@ pub fn lower_program_with_imported_names(
}
Err(mut errs) => errors.append(&mut errs),
},
Some("type") => match lower_type_alias(file, form) {
Ok(alias) => match alias_names.entry(alias.name.clone()) {
Entry::Vacant(entry) => {
entry.insert(alias.name_span);
type_aliases.push(alias);
}
Entry::Occupied(entry) => errors.push(
Diagnostic::new(
file,
"DuplicateTypeAlias",
format!("duplicate type alias `{}`", alias.name),
)
.with_span(alias.name_span)
.related("original type alias", *entry.get()),
),
},
Err(mut errs) => errors.append(&mut errs),
},
_ => {}
}
}
for alias in &type_aliases {
if is_reserved_type_name(&alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!(
"type alias `{}` conflicts with a built-in type name",
alias.name
),
)
.with_span(alias.name_span)
.hint("choose an alias name distinct from built-in type names"),
);
}
if let Some(struct_decl) = structs.iter().find(|decl| decl.name == alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!("type alias `{}` conflicts with a struct", alias.name),
)
.with_span(alias.name_span)
.related("struct declaration", struct_decl.name_span),
);
}
if let Some(enum_decl) = enums.iter().find(|decl| decl.name == alias.name) {
errors.push(
Diagnostic::new(
file,
"TypeAliasNameConflict",
format!("type alias `{}` conflicts with an enum", alias.name),
)
.with_span(alias.name_span)
.related("enum declaration", enum_decl.name_span),
);
}
}
for form in forms {
match list_head(form) {
Some("module") => match lower_module(file, form) {
@ -61,6 +120,7 @@ pub fn lower_program_with_imported_names(
},
Some("enum") => {}
Some("struct") => {}
Some("type") => {}
Some("import_c") => match lower_c_import(file, form) {
Ok(import) => c_imports.push(import),
Err(mut errs) => errors.append(&mut errs),
@ -106,6 +166,7 @@ pub fn lower_program_with_imported_names(
if errors.is_empty() {
Ok(Program {
module: module.unwrap_or_else(|| "main".to_string()),
type_aliases,
enums,
structs,
c_imports,
@ -124,6 +185,14 @@ pub fn print_program(program: &Program) -> String {
output.push_str(&program.module);
output.push('\n');
for alias in &program.type_aliases {
output.push_str(" type ");
output.push_str(&alias.name);
output.push_str(" = ");
output.push_str(&alias.target.to_string());
output.push('\n');
}
for enum_decl in &program.enums {
output.push_str(" enum ");
output.push_str(&enum_decl.name);
@ -543,6 +612,71 @@ fn lower_module(file: &str, form: &SExpr) -> Result<String, Diagnostic> {
})
}
fn lower_type_alias(file: &str, form: &SExpr) -> Result<TypeAliasDecl, Vec<Diagnostic>> {
let mut errors = Vec::new();
let Some(items) = expect_list(form) else {
return Err(vec![Diagnostic::new(
file,
"MalformedTypeAlias",
"type alias form must be a list",
)
.with_span(form.span)]);
};
if items.len() != 3 {
return Err(vec![Diagnostic::new(
file,
"MalformedTypeAlias",
"type alias form must be `(type Alias TargetType)`",
)
.with_span(form.span)
.expected("(type Alias TargetType)")]);
}
let name = match expect_ident(&items[1]) {
Some(name) => name.to_string(),
None => {
errors.push(
Diagnostic::new(
file,
"InvalidTypeAliasName",
"type alias name must be an identifier",
)
.with_span(items[1].span),
);
"<error>".to_string()
}
};
let target = match lower_type(&items[2]) {
Some(ty) => ty,
None => {
errors.push(
Diagnostic::new(
file,
"InvalidTypeAliasTarget",
"type alias target type is invalid",
)
.with_span(items[2].span)
.expected("concrete type"),
);
Type::Unit
}
};
if errors.is_empty() {
Ok(TypeAliasDecl {
name,
name_span: items[1].span,
target,
target_span: items[2].span,
span: form.span,
})
} else {
Err(errors)
}
}
fn lower_struct(file: &str, form: &SExpr) -> Result<StructDecl, Vec<Diagnostic>> {
let mut errors = Vec::new();
let Some(items) = expect_list(form) else {
@ -1249,6 +1383,26 @@ fn unsupported_unit_return_signature(file: &str, span: crate::token::Span) -> Di
.hint("`unit` is reserved for compiler/runtime unit-producing forms")
}
fn is_reserved_type_name(name: &str) -> bool {
matches!(
name,
"i32"
| "i64"
| "u32"
| "u64"
| "f64"
| "bool"
| "unit"
| "string"
| "ptr"
| "slice"
| "option"
| "result"
| "array"
| "vec"
)
}
fn lower_type(form: &SExpr) -> Option<Type> {
match &form.kind {
SExprKind::Atom(Atom::Ident(name)) => match name.as_str() {

View File

@ -177,6 +177,7 @@ struct ModuleUnit {
local_functions: HashMap<String, DeclInfo>,
local_structs: HashMap<String, DeclInfo>,
local_enums: HashMap<String, DeclInfo>,
local_aliases: HashMap<String, DeclInfo>,
}
#[derive(Debug, Clone)]
@ -204,6 +205,7 @@ enum DeclKind {
CImport,
Struct,
Enum,
TypeAlias,
}
#[derive(Debug, Clone)]
@ -2304,6 +2306,7 @@ fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit,
errors.append(&mut errs);
Program {
module: name.clone(),
type_aliases: Vec::new(),
enums: Vec::new(),
structs: Vec::new(),
c_imports: Vec::new(),
@ -2313,7 +2316,7 @@ fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit,
}
};
let (local_functions, local_structs, local_enums, mut duplicate_errors) =
let (local_functions, local_structs, local_enums, local_aliases, mut duplicate_errors) =
local_declarations(&file, &program);
errors.append(&mut duplicate_errors);
@ -2327,6 +2330,7 @@ fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit,
local_functions,
local_structs,
local_enums,
local_aliases,
})
} else {
Err(errors)
@ -2621,6 +2625,44 @@ fn external_enum_from_module(module: &ModuleUnit, name: &str) -> Option<External
})
}
fn external_enum_from_checked_module(
module: &ModuleUnit,
checked: &CheckedProgram,
name: &str,
) -> Option<ExternalEnum> {
let source_enum = module
.program
.enums
.iter()
.find(|enum_decl| enum_decl.name == name)?;
let checked_enum = checked
.enums
.iter()
.find(|enum_decl| enum_decl.name == name)?;
Some(ExternalEnum {
name: name.to_string(),
span: source_enum.name_span,
variants: checked_enum
.variants
.iter()
.map(|checked_variant| {
let source_variant = source_enum
.variants
.iter()
.find(|variant| variant.name == checked_variant.name);
ExternalEnumVariant {
name: checked_variant.name.clone(),
name_span: source_variant
.map_or(source_enum.name_span, |variant| variant.name_span),
payload_ty: checked_variant.payload_ty.clone(),
payload_ty_span: source_variant.and_then(|variant| variant.payload_ty_span),
}
})
.collect(),
})
}
fn local_declarations(
file: &str,
program: &Program,
@ -2628,14 +2670,44 @@ fn local_declarations(
HashMap<String, DeclInfo>,
HashMap<String, DeclInfo>,
HashMap<String, DeclInfo>,
HashMap<String, DeclInfo>,
Vec<Diagnostic>,
) {
let mut all = HashMap::<String, DeclInfo>::new();
let mut functions = HashMap::new();
let mut structs = HashMap::new();
let mut enums = HashMap::new();
let mut aliases = HashMap::new();
let mut errors = Vec::new();
for alias in &program.type_aliases {
let decl = DeclInfo {
span: alias.name_span,
kind: DeclKind::TypeAlias,
};
if let Some(original) = all.insert(alias.name.clone(), decl.clone()) {
if original.kind == DeclKind::CImport {
errors.push(
Diagnostic::new(
file,
"DuplicateTopLevelName",
format!("duplicate top-level name `{}`", alias.name),
)
.with_span(alias.name_span)
.related("original declaration", original.span),
);
} else {
errors.push(duplicate_name(
file,
&alias.name,
alias.name_span,
original.span,
));
}
}
aliases.insert(alias.name.clone(), decl);
}
for function in &program.functions {
let decl = DeclInfo {
span: function.span,
@ -2767,7 +2839,7 @@ fn local_declarations(
enums.insert(enum_decl.name.clone(), decl);
}
(functions, structs, enums, errors)
(functions, structs, enums, aliases, errors)
}
fn validate_workspace_packages(
@ -3028,9 +3100,20 @@ fn resolve_and_check_workspace(
let mut external_enums = Vec::new();
for (name, binding) in imports {
let provider = &packages[binding.provider_package].modules[binding.provider_module];
let checked_provider =
checked_by_module.get(&(binding.provider_package, provider.name.clone()));
match binding.kind {
DeclKind::Function => {
if let Some(function) =
if let Some(function) = checked_provider
.and_then(|program| program.functions.iter().find(|f| f.name == *name))
{
external_functions.push(ExternalFunction {
name: name.clone(),
params: function.params.iter().map(|(_, ty)| ty.clone()).collect(),
return_type: function.return_type.clone(),
foreign: false,
});
} else if let Some(function) =
provider.program.functions.iter().find(|f| f.name == *name)
{
external_functions.push(ExternalFunction {
@ -3042,7 +3125,16 @@ fn resolve_and_check_workspace(
}
}
DeclKind::CImport => {
if let Some(import) =
if let Some(import) = checked_provider
.and_then(|program| program.c_imports.iter().find(|f| f.name == *name))
{
external_functions.push(ExternalFunction {
name: name.clone(),
params: import.params.iter().map(|(_, ty)| ty.clone()).collect(),
return_type: import.return_type.clone(),
foreign: true,
});
} else if let Some(import) =
provider.program.c_imports.iter().find(|f| f.name == *name)
{
external_functions.push(ExternalFunction {
@ -3054,7 +3146,15 @@ fn resolve_and_check_workspace(
}
}
DeclKind::Struct => {
if let Some(struct_decl) =
if let Some(struct_decl) = checked_provider
.and_then(|program| program.structs.iter().find(|s| s.name == *name))
{
external_structs.push(ExternalStruct {
name: name.clone(),
fields: struct_decl.fields.clone(),
span: struct_decl.span,
});
} else if let Some(struct_decl) =
provider.program.structs.iter().find(|s| s.name == *name)
{
external_structs.push(ExternalStruct {
@ -3069,10 +3169,16 @@ fn resolve_and_check_workspace(
}
}
DeclKind::Enum => {
if let Some(enum_decl) = external_enum_from_module(provider, name) {
if let Some(enum_decl) = checked_provider
.and_then(|program| {
external_enum_from_checked_module(provider, program, name)
})
.or_else(|| external_enum_from_module(provider, name))
{
external_enums.push(enum_decl);
}
}
DeclKind::TypeAlias => {}
}
}
@ -3243,9 +3349,19 @@ fn resolve_and_check(
let mut external_enums = Vec::new();
for (name, binding) in imports {
let provider = &modules[by_name[&binding.provider]];
let checked_provider = checked_by_module.get(&binding.provider);
match binding.kind {
DeclKind::Function => {
if let Some(function) =
if let Some(function) = checked_provider
.and_then(|program| program.functions.iter().find(|f| f.name == *name))
{
external_functions.push(ExternalFunction {
name: name.clone(),
params: function.params.iter().map(|(_, ty)| ty.clone()).collect(),
return_type: function.return_type.clone(),
foreign: false,
});
} else if let Some(function) =
provider.program.functions.iter().find(|f| f.name == *name)
{
external_functions.push(ExternalFunction {
@ -3257,7 +3373,16 @@ fn resolve_and_check(
}
}
DeclKind::CImport => {
if let Some(import) =
if let Some(import) = checked_provider
.and_then(|program| program.c_imports.iter().find(|f| f.name == *name))
{
external_functions.push(ExternalFunction {
name: name.clone(),
params: import.params.iter().map(|(_, ty)| ty.clone()).collect(),
return_type: import.return_type.clone(),
foreign: true,
});
} else if let Some(import) =
provider.program.c_imports.iter().find(|f| f.name == *name)
{
external_functions.push(ExternalFunction {
@ -3269,7 +3394,15 @@ fn resolve_and_check(
}
}
DeclKind::Struct => {
if let Some(struct_decl) =
if let Some(struct_decl) = checked_provider
.and_then(|program| program.structs.iter().find(|s| s.name == *name))
{
external_structs.push(ExternalStruct {
name: name.clone(),
fields: struct_decl.fields.clone(),
span: struct_decl.span,
});
} else if let Some(struct_decl) =
provider.program.structs.iter().find(|s| s.name == *name)
{
external_structs.push(ExternalStruct {
@ -3284,10 +3417,16 @@ fn resolve_and_check(
}
}
DeclKind::Enum => {
if let Some(enum_decl) = external_enum_from_module(provider, name) {
if let Some(enum_decl) = checked_provider
.and_then(|program| {
external_enum_from_checked_module(provider, program, name)
})
.or_else(|| external_enum_from_module(provider, name))
{
external_enums.push(enum_decl);
}
}
DeclKind::TypeAlias => {}
}
}
@ -3381,6 +3520,22 @@ fn build_export_maps(
Some(kind) => {
exports.insert(export.name.clone(), kind);
}
None if module.local_aliases.contains_key(&export.name) => diagnostics.push(
Diagnostic::new(
&module.file,
"Visibility",
format!(
"type alias `{}` is module-local and cannot be exported",
export.name
),
)
.with_span(export.span)
.related(
"type alias declaration",
module.local_aliases[&export.name].span,
)
.hint("export functions, structs, or enums; imported signatures see concrete target types"),
),
None => diagnostics.push(
Diagnostic::new(
&module.file,
@ -3428,6 +3583,7 @@ fn resolve_imports(
.get(&name.name)
.or_else(|| module.local_structs.get(&name.name))
.or_else(|| module.local_enums.get(&name.name))
.or_else(|| module.local_aliases.get(&name.name))
{
diagnostics.push(duplicate_name(
&module.file,
@ -3470,7 +3626,8 @@ fn resolve_imports(
.local_functions
.get(&name.name)
.or_else(|| provider.local_structs.get(&name.name))
.or_else(|| provider.local_enums.get(&name.name));
.or_else(|| provider.local_enums.get(&name.name))
.or_else(|| provider.local_aliases.get(&name.name));
let exported = export_maps[provider_index].get(&name.name).copied();
match (provider_local, exported) {
(_, Some(kind)) => {
@ -3483,6 +3640,23 @@ fn resolve_imports(
},
);
}
(Some(decl), None) if decl.kind == DeclKind::TypeAlias => diagnostics.push(
Diagnostic::new(
&module.file,
"Visibility",
format!(
"type alias `{}` is module-local and cannot be imported from module `{}`",
name.name, provider.name
),
)
.with_span(name.span)
.related_in_file(
provider.file.clone(),
"type alias declaration",
decl.span,
)
.hint("import functions, structs, or enums; function signatures expose the alias target type"),
),
(Some(decl), None) => diagnostics.push(
Diagnostic::new(
&module.file,
@ -3561,6 +3735,7 @@ fn resolve_workspace_imports(
.get(&name.name)
.or_else(|| module.local_structs.get(&name.name))
.or_else(|| module.local_enums.get(&name.name))
.or_else(|| module.local_aliases.get(&name.name))
{
diagnostics.push(duplicate_name(
&module.file,
@ -3608,7 +3783,8 @@ fn resolve_workspace_imports(
.local_functions
.get(&name.name)
.or_else(|| provider.local_structs.get(&name.name))
.or_else(|| provider.local_enums.get(&name.name));
.or_else(|| provider.local_enums.get(&name.name))
.or_else(|| provider.local_aliases.get(&name.name));
let exported = export_maps[provider_package_index]
[provider_module_index]
.get(&name.name)
@ -3625,6 +3801,25 @@ fn resolve_workspace_imports(
},
);
}
(Some(decl), None) if decl.kind == DeclKind::TypeAlias => {
diagnostics.push(
Diagnostic::new(
&module.file,
"Visibility",
format!(
"type alias `{}` is module-local and cannot be imported from module `{}`",
name.name, import.module
),
)
.with_span(name.span)
.related_in_file(
provider.file.clone(),
"type alias declaration",
decl.span,
)
.hint("import functions, structs, or enums; function signatures expose the alias target type"),
);
}
(Some(decl), None) => diagnostics.push(
Diagnostic::new(
&module.file,

View File

@ -0,0 +1,116 @@
use std::{
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Output},
};
#[test]
fn concrete_type_alias_fixture_erases_before_llvm_and_runs_tests() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.slo");
let compile = run_glagol([fixture.as_os_str()]);
let stdout = String::from_utf8_lossy(&compile.stdout);
let stderr = String::from_utf8_lossy(&compile.stderr);
assert!(
compile.status.success(),
"compiler rejected concrete type alias fixture\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr
);
assert!(
stdout.contains("define i32 @bump(i32 %value)")
&& stdout.contains("define [3 x i32] @make_counts(i32 %base)")
&& stdout.contains("define i32 @pick_count([3 x i32] %values)")
&& stdout.contains("define { i32, ptr } @make_measure(i32 %amount)")
&& stdout.contains("define { i1, i32 } @maybe_amount(i32 %amount)")
&& stdout.contains("define { i1, i32 } @ok_amount(i32 %amount)")
&& stdout.contains("define ptr @empty_counts()")
&& stdout.contains("define { i32, i32 } @reading_value(i32 %amount)")
&& stdout.contains("define i32 @reading_score({ i32, i32 } %reading)")
&& stdout.contains("call ptr @__glagol_vec_i32_empty()"),
"LLVM output did not show erased concrete alias shapes\nstdout:\n{}",
stdout
);
assert!(
!stdout.contains("Count")
&& !stdout.contains("Score")
&& !stdout.contains("MaybeCount")
&& !stdout.contains("ReadingAlias"),
"LLVM output leaked source alias names\nstdout:\n{}",
stdout
);
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
assert_success_stdout(
run,
concat!(
"test \"aliases erase through arrays and structs\" ... ok\n",
"test \"aliases erase through option result vec and enum\" ... ok\n",
"2 test(s) passed\n",
),
);
}
#[test]
fn concrete_type_alias_fixture_formats_and_lowers_stably() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.slo");
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
assert_success_stdout(
formatted,
&std::fs::read_to_string(&fixture).expect("read type alias fixture"),
);
let surface = run_glagol([
OsStr::new("--inspect-lowering=surface"),
fixture.as_os_str(),
]);
assert_success_stdout(
surface,
&std::fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.surface.lower"),
)
.expect("read type alias surface snapshot"),
);
let checked = run_glagol([
OsStr::new("--inspect-lowering=checked"),
fixture.as_os_str(),
]);
assert_success_stdout(
checked,
&std::fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.checked.lower"),
)
.expect("read type alias checked snapshot"),
);
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(glagol_bin())
.args(args)
.output()
.expect("run glagol")
}
fn glagol_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_glagol"))
}
fn assert_success_stdout(output: Output, expected: &str) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"command failed\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr
);
assert_eq!(stdout, expected);
assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr);
}

View File

@ -83,6 +83,98 @@ const CASES: &[DiagnosticCase] = &[
"#,
snapshot: "../tests/unknown-top-level-form.diag",
},
DiagnosticCase {
name: "malformed-type-alias",
source: r#"
(module main)
(type Count)
"#,
snapshot: "../tests/malformed-type-alias.diag",
},
DiagnosticCase {
name: "duplicate-type-alias",
source: r#"
(module main)
(type Count i32)
(type Count i64)
"#,
snapshot: "../tests/duplicate-type-alias.diag",
},
DiagnosticCase {
name: "type-alias-name-conflict",
source: r#"
(module main)
(struct Count
(value i32))
(type Count i32)
"#,
snapshot: "../tests/type-alias-name-conflict.diag",
},
DiagnosticCase {
name: "type-alias-value-name-conflict",
source: r#"
(module main)
(import_c c_add ((value i32)) -> i32)
(type main i32)
(type c_add i32)
(fn main () -> i32
0)
"#,
snapshot: "../tests/type-alias-value-name-conflict.diag",
},
DiagnosticCase {
name: "unknown-type-alias-target",
source: r#"
(module main)
(type Count Missing)
"#,
snapshot: "../tests/unknown-type-alias-target.diag",
},
DiagnosticCase {
name: "unsupported-type-alias-target",
source: r#"
(module main)
(type BadUnit unit)
(type BadPtr (ptr i32))
(type BadSlice (slice i32))
(type BadVec (vec u32))
(type BadResult (result i32 string))
"#,
snapshot: "../tests/unsupported-type-alias-target.diag",
},
DiagnosticCase {
name: "self-type-alias",
source: r#"
(module main)
(type Count Count)
"#,
snapshot: "../tests/self-type-alias.diag",
},
DiagnosticCase {
name: "cyclic-type-alias",
source: r#"
(module main)
(type A B)
(type B A)
"#,
snapshot: "../tests/cyclic-type-alias.diag",
},
DiagnosticCase {
name: "enum-empty",
source: r#"

View File

@ -13,6 +13,12 @@ const LOWERING_FIXTURES: &[LoweringFixture] = &[
surface_snapshot: "../tests/top-level-test.surface.lower",
checked_snapshot: "../tests/top-level-test.checked.lower",
},
LoweringFixture {
name: "type-aliases",
source: "../tests/type-aliases.slo",
surface_snapshot: "../tests/type-aliases.surface.lower",
checked_snapshot: "../tests/type-aliases.checked.lower",
},
LoweringFixture {
name: "comments",
source: "../tests/comments.slo",

View File

@ -1279,6 +1279,63 @@ fn enum_import_visibility_and_duplicate_cases_are_diagnostics() {
);
}
#[test]
fn type_aliases_are_local_across_project_visibility() {
let erased_signature = write_project(
"alias-erased-signature",
&[(
"types",
"(module types (export make_count))\n\n(type Count i32)\n\n(fn make_count ((value Count)) -> Count\n value)\n",
)],
"(module main)\n\n(import types (make_count))\n\n(fn main () -> i32\n (make_count 42))\n",
);
let erased_output = run_glagol(["check".as_ref(), erased_signature.as_os_str()]);
assert_success_stdout("alias erased project signature", erased_output, "");
let export_alias = write_project(
"alias-export",
&[(
"types",
"(module types (export Count))\n\n(type Count i32)\n\n(fn make_count ((value Count)) -> Count\n value)\n",
)],
"(module main)\n",
);
let export_output = run_glagol(["check".as_ref(), export_alias.as_os_str()]);
assert_exit_code("alias export", &export_output, 1);
assert_stderr_contains("alias export", &export_output, "Visibility");
assert_stderr_contains(
"alias export message",
&export_output,
"type alias `Count` is module-local and cannot be exported",
);
let import_alias = write_project(
"alias-import",
&[("types", "(module types)\n\n(type Count i32)\n")],
"(module main)\n\n(import types (Count))\n",
);
let import_output = run_glagol(["check".as_ref(), import_alias.as_os_str()]);
assert_exit_code("alias import", &import_output, 1);
assert_stderr_contains("alias import", &import_output, "Visibility");
assert_stderr_contains(
"alias import message",
&import_output,
"type alias `Count` is module-local and cannot be imported",
);
let duplicate_local = write_project(
"alias-import-duplicate",
&[(
"types",
"(module types (export value))\n\n(fn value () -> i32\n 1)\n",
)],
"(module main)\n\n(import types (Count))\n\n(type Count i32)\n",
);
let duplicate_output = run_glagol(["check".as_ref(), duplicate_local.as_os_str()]);
assert_exit_code("alias import duplicate", &duplicate_output, 1);
assert_stderr_contains("alias import duplicate", &duplicate_output, "DuplicateName");
}
#[test]
fn project_diagnostic_families_have_json_golden_coverage() {
let duplicate = write_project(

View File

@ -13,11 +13,13 @@ const DIAGNOSTIC_SNAPSHOTS: &[&str] = &[
"array-return-type-mismatch.diag",
"arity-mismatch.diag",
"cyclic-struct-fields.diag",
"cyclic-type-alias.diag",
"duplicate-local.diag",
"duplicate-match-arm.diag",
"duplicate-struct-constructor-field.diag",
"duplicate-struct-field.diag",
"duplicate-struct.diag",
"duplicate-type-alias.diag",
"empty-array.diag",
"enum-constructor-arity.diag",
"enum-container-values.diag",
@ -65,6 +67,7 @@ const DIAGNOSTIC_SNAPSHOTS: &[&str] = &[
"malformed-result-err-unwrap.diag",
"malformed-result-ok-unwrap.diag",
"malformed-result-constructor.diag",
"malformed-type-alias.diag",
"malformed-unsafe-form.diag",
"malformed-while.diag",
"field-access-on-non-struct.diag",
@ -82,6 +85,7 @@ const DIAGNOSTIC_SNAPSHOTS: &[&str] = &[
"set-immutable-local.diag",
"set-parameter.diag",
"single-file-main-i64-return.diag",
"self-type-alias.diag",
"set-type-mismatch.diag",
"set-unknown-local.diag",
"std-abi-layout-unsupported.diag",
@ -280,7 +284,11 @@ const DIAGNOSTIC_SNAPSHOTS: &[&str] = &[
"test-invalid-name.diag",
"test-non-bool.diag",
"type-mismatch.diag",
"type-alias-name-conflict.diag",
"type-alias-value-name-conflict.diag",
"unclosed-list.diag",
"unknown-type-alias-target.diag",
"unsupported-type-alias-target.diag",
"unknown-function.diag",
"unknown-struct-local.diag",
"unknown-struct-parameter.diag",
@ -465,6 +473,8 @@ const LOWERING_INSPECTOR_FIXTURES: &[&str] = &[
"time-sleep.surface.lower",
"top-level-test.checked.lower",
"top-level-test.surface.lower",
"type-aliases.checked.lower",
"type-aliases.surface.lower",
"u32-numeric-primitive.checked.lower",
"u32-numeric-primitive.surface.lower",
"u64-numeric-primitive.checked.lower",
@ -490,6 +500,11 @@ const LOWERING_INSPECTOR_CASES: &[LoweringInspectorCase] = &[
surface_snapshot: "top-level-test.surface.lower",
checked_snapshot: "top-level-test.checked.lower",
},
LoweringInspectorCase {
source: "tests/type-aliases.slo",
surface_snapshot: "type-aliases.surface.lower",
checked_snapshot: "type-aliases.checked.lower",
},
LoweringInspectorCase {
source: "tests/comments.slo",
surface_snapshot: "comments.surface.lower",
@ -853,6 +868,7 @@ const GLAGOL_TEST_FIXTURES: &[&str] = &[
"time-sleep.slo",
"top-level-test.fmt",
"top-level-test.slo",
"type-aliases.slo",
"u32-numeric-primitive.slo",
"u64-numeric-primitive.slo",
"unsafe.slo",
@ -920,6 +936,7 @@ const SLOVO_FORMATTER_FIXTURES: &[&str] = &[
"struct.slo",
"time-sleep.slo",
"top-level-test.slo",
"type-aliases.slo",
"u32-numeric-primitive.slo",
"u64-numeric-primitive.slo",
"unsafe.slo",

View File

@ -6,6 +6,7 @@ use std::{
path::{Path, PathBuf},
process::{Command, Output, Stdio},
sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
@ -951,11 +952,16 @@ fn write_fixture(name: &str, source: &str) -> PathBuf {
}
fn temp_root(name: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before Unix epoch")
.as_nanos();
env::temp_dir().join(format!(
"glagol-exp10-host-result-{}-{}-{}",
"glagol-exp10-host-result-{}-{}-{}-{}",
name,
std::process::id(),
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed),
nanos
))
}

View File

@ -105,7 +105,8 @@ Candidate features:
- `glagol run`-friendly `main` conventions and clearer entry-point diagnostics
- better `match` diagnostics and exhaustiveness checks where the current enum
model allows it
- type aliases for long concrete types such as vectors, options, and results
- concrete type aliases for long concrete types such as vectors, options, and
results
- destructuring for simple structs and enum payloads
- additional numeric completeness such as `f32`, only if it can be tested across
checker, formatter, runtime, and docs
@ -117,6 +118,8 @@ Released in `1.0.0-beta.4`: project/workspace build and run entry
diagnostics now use entry-specific codes and explicitly show the required
`(fn main () -> i32 ...)` contract. Non-exhaustive `match` diagnostics now use
clearer missing-arm wording and deterministic found-arm output.
Concrete aliases were split into the follow-up `1.0.0-beta.8` language slice so
the syntax, formatter, diagnostics, and source fixtures could be gated directly.
### 5. Package And Workspace Discipline
@ -194,7 +197,37 @@ encoding policy beyond the current runtime string ABI remain deferred.
Why seventh: networking and CLI tools need data interchange, but a complete JSON
library depends on collection work.
### 8. Generics And Collection Unification
### 8. Concrete Type Alias Foundation
Goal: reduce concrete type repetition without introducing generics or changing
runtime representation.
Work:
- add top-level `(type Alias TargetType)` declarations for aliases whose targets
are already supported concrete Slovo types
- resolve aliases before typed-core lowering, checked import signatures, backend
layout, ABI decisions, and runtime behavior
- keep aliases module-local: no alias exports, imports, re-exports, or
cross-module alias visibility
- update formatter and diagnostics for malformed, duplicate, unsupported,
cyclic, exported, or imported aliases
- exercise aliases sparingly in JSON source facades and explicit source
fixtures without adding compiler-known runtime names
Released in `1.0.0-beta.8`: concrete aliases such as `(type JsonText string)`
are transparent names for existing concrete types. The compiler parses,
formats, checks, lowers, and erases aliases before backend behavior, while
project imports of functions that used local aliases see concrete target
types. Alias export/import attempts and unsupported targets are diagnostics.
Generic aliases, parameterized aliases, aliasing maps/sets, stable ABI/layout
names, and runtime changes remain deferred.
Why eighth: concrete aliases remove real noise from current stdlib and fixture
code while deliberately postponing generic type parameters until the compiler
and standard library have stronger design pressure.
### 9. Generics And Collection Unification
Goal: stop duplicating every helper across concrete vector, option, and result
families.
@ -207,11 +240,11 @@ Work:
- add generic arrays/vectors first, then maps and sets
- keep stable ABI/layout promises deferred until after real beta use
Why eighth: generics are important, but they should be introduced after the
Why ninth: generics are important, but they should be introduced after the
compiler, docs, tests, and stdlib have enough real pressure to validate the
design.
### 9. Developer Experience
### 10. Developer Experience
Goal: make Slovo comfortable for repeated daily use.
@ -223,7 +256,7 @@ Work:
- clearer benchmark harness output
- machine-readable diagnostics stability policy
Why ninth: editor support matters, but it should follow a stable enough syntax,
Why tenth: editor support matters, but it should follow a stable enough syntax,
project model, and diagnostic model.
## Stable `1.0.0` Gate

View File

@ -10,7 +10,45 @@ integration/readiness release, not the first real beta.
## Unreleased
No unreleased changes yet.
Next scoped Glagol work is expected to use the `1.0.0-beta.9` release line.
Generics and collection unification are the likely next design pressure, but
remain unreleased until their contracts and gates are explicit.
No unreleased compiler scope is committed here yet.
## 1.0.0-beta.8
Release label: `1.0.0-beta.8`
Release date: 2026-05-22
Release state: released beta concrete type alias foundation update
### Summary
Glagol `1.0.0-beta.8` is scoped to transparent concrete type aliases. A source
module may declare `(type Alias TargetType)` for an existing supported concrete
type and then use `Alias` in supported type positions. The alias is not a new
runtime type and must be normalized before backend/ABI behavior.
- Parse top-level `(type Alias TargetType)` declarations.
- Resolve aliases to existing supported concrete target types before typed-core
lowering, checked import signatures, LLVM/backend layout, ABI decisions, and
runtime behavior.
- Format aliases canonically as one-line top-level declarations.
- Diagnose malformed aliases, duplicate alias/type/value names, unknown or
unsupported target types, cycles, exported aliases, imported aliases, and
parameterized/generic alias attempts.
- Keep runtime behavior unchanged: no new compiler-known runtime names, hosted
symbols, maps/sets, generic aliases, parameterized aliases, or cross-module
alias visibility.
### Explicit Deferrals
This release does not add generics, parameterized aliases, alias
exports/imports, alias re-exports, cross-module alias visibility, maps/sets,
stable layout names, runtime helpers, hosted runtime symbols, or
standard-library API freeze claims.
## 1.0.0-beta.7

View File

@ -22,8 +22,8 @@ general-purpose beta release.
A Glagol feature is done only when it has parser/lowerer support, checker behavior, diagnostics for invalid forms, backend behavior or explicit unsupported diagnostics, and tests.
Current stage: `1.0.0-beta.7`, released on 2026-05-22 as the first post-beta
serialization/data-interchange foundation update. It keeps the `1.0.0-beta` language/compiler
Current stage: `1.0.0-beta.8`, released on 2026-05-22 as the first post-beta
concrete type alias foundation update. It keeps the `1.0.0-beta` language/compiler
support baseline and includes the `1.0.0-beta.1` tooling hardening release, the
`1.0.0-beta.2` runtime/resource foundation release, the `1.0.0-beta.3`
standard-library stabilization release, the `1.0.0-beta.4`
@ -32,7 +32,14 @@ discipline release, and the `1.0.0-beta.6` compiler-known `std.net` loopback
TCP runtime family with focused lowering, interpreter, diagnostics,
source-facade, promotion, and hosted smoke coverage, plus the `1.0.0-beta.7`
compiler-known `std.json.quote_string` runtime family with matching
source-facade, test-runner, hosted smoke, and benchmark-scaffold coverage.
source-facade, test-runner, hosted smoke, and benchmark-scaffold coverage, plus
top-level `(type Alias TargetType)` parsing, checking, formatting, lowering
inspection, project import normalization, and diagnostics coverage for concrete
aliases.
Next stage target: `1.0.0-beta.9`, likely generics and collection
unification. The exact compiler-side contract must be frozen from the manifest
and roadmap before implementation.
The final experimental precursor scope is `exp-125`. Its unsigned direct-value
flow, parse/format runtime lanes, and matching staged stdlib helper breadth

View File

@ -8,7 +8,7 @@ Historical `exp-*` releases listed here are experimental maturity milestones.
The pushed tag `v2.0.0-beta.1` is historical. It is now documented as an
experimental integration/readiness release, not as a beta maturity claim.
The current release is `1.0.0-beta.7`, published on 2026-05-22. It keeps the
The current release is `1.0.0-beta.8`, published on 2026-05-22. It keeps the
`1.0.0-beta` language surface, includes the first post-beta tooling/install
hardening bundle from `1.0.0-beta.1`, and adds the first runtime/resource
foundation bundle from `1.0.0-beta.2` plus the first standard-library
@ -16,11 +16,49 @@ stabilization bundle from `1.0.0-beta.3`, the first language-usability
diagnostics bundle from `1.0.0-beta.4`, and the first local
package/workspace discipline bundle from `1.0.0-beta.5`, plus the first
loopback networking foundation bundle from `1.0.0-beta.6`, and the first
serialization/data-interchange foundation bundle from `1.0.0-beta.7`.
serialization/data-interchange foundation bundle from `1.0.0-beta.7`, and the
first concrete type alias foundation from `1.0.0-beta.8`.
## Unreleased
No unreleased changes yet.
Next scoped Slovo-side work is expected to use the `1.0.0-beta.9` release
line. Generics and collection unification are the likely next design pressure,
but remain unreleased until their contracts and gates are explicit.
No unreleased language scope is committed here yet.
## 1.0.0-beta.8
Release label: `1.0.0-beta.8`
Release name: Concrete Type Alias Foundation
Release date: 2026-05-22
Status: released beta concrete type alias foundation update on the
`1.0.0-beta` language baseline.
`1.0.0-beta.8` promotes transparent type aliases for current concrete types:
```slo
(type JsonText string)
```
An alias is a module-local source name. It may be used in supported type
positions, and the compiler resolves it to the existing concrete target type
before backend and ABI behavior. This release does not add generics,
parameterized aliases, cross-module alias imports, alias re-exports, maps/sets,
stable layout names, new compiler-known runtime names, or runtime changes.
- Top-level `(type Alias TargetType)` declarations name existing concrete
supported target types without introducing new runtime representations.
- Aliases are transparent before typed-core lowering, checked import
signatures, backend layout, ABI decisions, and runtime behavior.
- The JSON source facade and explicit JSON example projects use local
`JsonText` / `JsonField` aliases as narrow source fixtures.
- Alias exports, imports, re-exports, cross-module alias visibility, generic
aliases, parameterized aliases, maps/sets, and runtime changes remain out of
scope.
## 1.0.0-beta.7

View File

@ -10,19 +10,25 @@ Long-horizon planning lives in
release train from the historical `v2.0.0-beta.1` tag toward and beyond the
first real general-purpose beta Slovo contract.
Current stage: `1.0.0-beta.7`, released on 2026-05-22 as the first post-beta
serialization/data-interchange foundation update. It keeps the `1.0.0-beta` language contract and
Current stage: `1.0.0-beta.8`, released on 2026-05-22 as the first post-beta
concrete type alias foundation update. It keeps the `1.0.0-beta` language contract and
includes the `1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2`
runtime/resource foundation release, the `1.0.0-beta.3` standard-library
stabilization release, the `1.0.0-beta.4` language-usability diagnostics
release, the `1.0.0-beta.5` package/workspace discipline release, and a narrow
`std.net` source facade for blocking loopback TCP client/server primitives over
opaque `i32` handles and concrete `result` families, plus a narrow `std.json`
source facade for compact JSON text construction. JSON parsing, recursive JSON
values, maps/sets, DNS, TLS, UDP, async IO, non-loopback binding, HTTP
frameworks, rich host-error ADTs, stable ABI/layout, and a stable
source facade for compact JSON text construction, plus top-level module-local
transparent aliases for supported concrete target types. JSON parsing,
recursive JSON values, generic aliases, parameterized aliases, cross-module
alias visibility, maps/sets, DNS, TLS, UDP, async IO, non-loopback binding,
HTTP frameworks, rich host-error ADTs, stable ABI/layout, and a stable
standard-library API freeze remain deferred.
Next stage target: `1.0.0-beta.9`, likely generics and collection
unification. The exact scope must be frozen from the manifest and roadmap
before implementation.
The final experimental precursor scope is `exp-125`, defined in
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its
unsigned direct-value flow, parse/format runtime lanes, and matching staged

View File

@ -107,6 +107,13 @@ Current v1 release surface and explicit experimental targets:
v1.1-v1.7 surface is supported for small real flat local projects through
`glagol new`, `check`, `fmt --check/--write`, `test`, `build`, `doc`,
artifact manifests, JSON diagnostics, and the release-gate script
- `1.0.0-beta.8` concrete type alias target: top-level
`(type Alias TargetType)` declarations name existing supported concrete
target types and are resolved before typed-core lowering, backend layout, ABI
decisions, and runtime behavior; no generic aliases, parameterized
aliases, alias exports/imports/re-exports, cross-module alias visibility,
maps/sets, or runtime changes are included, once the matching Glagol gates
pass
- `exp-1` owned runtime strings: compiler-known `std.string.concat` accepts two
`string` values and returns an immutable runtime-owned `string`; existing
string equality, length, printing, locals, parameters, returns, and calls work
@ -1108,6 +1115,71 @@ schema validation, Unicode normalization, stable text encoding policy beyond
the current null-terminated runtime string ABI, stable runtime helper symbols,
and stable standard-library API guarantees remain deferred.
### 4.4.6 Post-Beta Concrete Type Alias Foundation
Status: released in `1.0.0-beta.8` with matching Glagol parser, checker,
formatter, diagnostics, fixture, documentation, and promotion gates.
`1.0.0-beta.8` promotes transparent aliases for existing concrete type forms:
```slo
(type JsonText string)
(type Scores (vec i32))
(type MaybeName (option string))
(type ReadResult (result string i32))
```
The declaration form is exactly `(type Alias TargetType)` at top level. `Alias`
must be a single user type name. `TargetType` must resolve to an already
supported concrete Slovo type: direct scalar and string types, current fixed
array families, current concrete vec families, current concrete option/result
families, current known struct names, or current enum names where that type is
already legal in the use position.
Aliases are transparent. Using `Alias` in a type position is equivalent to
using its resolved target type. The resolved target controls value flow,
operators, constructors, `match`, field access, imports of functions that use
the type, lowering, runtime behavior, and diagnostics. The alias does not create
a nominal type, wrapper, cast, runtime tag, layout name, hosted symbol, or C ABI
boundary.
Alias declarations are module-local. They may be used by later declarations in
the same module and by exported functions or structs after normalization, but
the alias name itself is not exportable, importable, or re-exportable. A module
that imports a function whose signature used a local alias sees the normalized
concrete target type, not the alias name.
Name resolution for type positions checks local concrete aliases alongside
local struct and enum type names before applying builtin/concrete type forms.
Duplicate alias, struct, enum, function, import-list, export-list, local, or
parameter names remain diagnostics under the existing duplicate-name policy.
Aliases must not shadow builtins, compiler-known standard-runtime names, or
reserved unsafe heads.
Formatter output keeps aliases as one-line top-level declarations:
```slo
(type JsonText string)
```
An alias target that is already a parenthesized concrete type form stays inline,
for example `(type JsonItems (vec string))`. Comments are allowed around the
top-level alias declaration under the existing full-line comment rules, not
inside the declaration form.
Required diagnostics include malformed alias declarations, duplicate alias
names, unknown target types, unsupported target types, cyclic aliases,
parameterized alias attempts, exported aliases, imported aliases, and
cross-module alias references. An implementation may use precise diagnostic
codes, but it must not silently treat an alias as `string`, `i32`, or any other
fallback type after a failed resolution.
This target explicitly does not add generic aliases, parameterized aliases,
alias type parameters, higher-kinded aliases, alias re-exports, cross-module
alias imports, import aliases, glob imports, maps/sets, alias-driven overloads,
implicit casts, new runtime helpers, standard-runtime names, stable ABI/layout
promises, or a stable standard-library API freeze.
## 4.5 v2.0.0-beta.1 Experimental Integration Readiness
Status: current experimental Slovo-side release contract, released 2026-05-17.
@ -5442,6 +5514,7 @@ comments inside:
- `(module ...)` forms
- `(struct ...)` forms
- `(type ...)` alias declaration forms
- `fn` and `test` headers before their body begins
- type forms such as `(array i32 N)`, `(option i32)`, `(result i32 i32)`,
exp-2 `(vec i32)`, exp-94 `(vec i64)`, exp-103 `(vec f64)`, and exp-4

View File

@ -6,7 +6,7 @@ Do not edit this file by hand.
## Stability Tiers
- `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.7`; future releases may mark new helpers this way before they graduate.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.8`; future releases may mark new helpers this way before they graduate.
- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.
The catalog is a beta compatibility aid, not a stable `1.0.0` API freeze.

View File

@ -12,6 +12,8 @@ runtime/resource foundation APIs. `1.0.0-beta.3` adds the generated stdlib API
catalog and the checked `projects/stdlib-composition/` example.
`1.0.0-beta.7` adds explicit `projects/std-import-json/` and
`projects/std-layout-local-json/` examples for compact JSON text construction.
`1.0.0-beta.8` reuses those JSON projects for local `JsonText`/`JsonField`
concrete type alias fixtures.
The language
baseline absorbs the final exp-125 unsigned precursor scope alongside the
already promoted project/package, stdlib-source, collection, composite-data,

View File

@ -0,0 +1,79 @@
(module main)
(type Count i32)
(type Score Count)
(type Text string)
(type Counts (array Count 3))
(type CountVec (vec Count))
(type MaybeCount (option Count))
(type CountResult (result Count Count))
(struct Measurement
(amount Count)
(label Text))
(type Measure Measurement)
(enum Reading
Empty
(Value Count))
(type ReadingAlias Reading)
(fn bump ((value Count)) -> Score
(+ value 1))
(fn make_counts ((base Count)) -> Counts
(array Count base (+ base 1) (+ base 2)))
(fn pick_count ((values Counts)) -> Count
(index values 1))
(fn make_measure ((amount Count)) -> Measure
(Measurement (amount amount) (label "ok")))
(fn measure_amount ((measure Measure)) -> Count
(. measure amount))
(fn maybe_amount ((amount Count)) -> MaybeCount
(some Count amount))
(fn maybe_empty () -> MaybeCount
(none Count))
(fn ok_amount ((amount Count)) -> CountResult
(ok Count Count amount))
(fn err_amount ((code Count)) -> CountResult
(err Count Count code))
(fn empty_counts () -> CountVec
(std.vec.i32.empty))
(fn reading_value ((amount Count)) -> ReadingAlias
(Reading.Value amount))
(fn reading_score ((reading ReadingAlias)) -> Count
(match reading
((Reading.Empty)
0)
((Reading.Value amount)
amount)))
(fn main () -> i32
(let values Counts (make_counts 40))
(+ (pick_count values) (measure_amount (make_measure 1))))
(test "aliases erase through arrays and structs"
(= (main) 42))
(test "aliases erase through option result vec and enum"
(let maybe MaybeCount (maybe_amount 7))
(let result CountResult (ok_amount 8))
(and (is_some maybe) (and (is_ok result) (and (= (std.vec.i32.len (empty_counts)) 0) (= (reading_score (reading_value 9)) 9)))))

View File

@ -5,7 +5,7 @@
Sanjin Gumbarevic<br>
hermeticum_lab@protonmail.com
Publication release: `1.0.0-beta.7`
Publication release: `1.0.0-beta.8`
Technical behavior baseline: compiler and language support through
`1.0.0-beta`; tooling and install workflow through `1.0.0-beta.1`;
@ -13,12 +13,13 @@ runtime/resource foundation through `1.0.0-beta.2`; standard-library
stabilization through `1.0.0-beta.3`; language-usability diagnostics through
`1.0.0-beta.4`; package/workspace discipline through `1.0.0-beta.5`;
loopback networking foundation through `1.0.0-beta.6`;
serialization/data-interchange foundation through `1.0.0-beta.7`
serialization/data-interchange foundation through `1.0.0-beta.7`;
concrete type alias foundation through `1.0.0-beta.8`
Date: 2026-05-22
Evidence source: paired local Slovo/Glagol monorepo verification and benchmark
reruns from a local checkout; beta.7 release-gate verification from the public
reruns from a local checkout; beta.8 release-gate verification from the public
monorepo
Maturity: beta
@ -30,23 +31,26 @@ Slovo. It exists to make the language support boundary inspectable: tokens,
S-expression tree, AST, typed AST, LLVM IR, hosted native executable, tests,
diagnostics, and release documents should agree.
The current publication release, `1.0.0-beta.7`, keeps the first real
The current publication release, `1.0.0-beta.8`, keeps the first real
general-purpose beta toolchain baseline from `1.0.0-beta` and records the
first post-beta tooling/install hardening update plus the first
runtime/resource foundation update plus the first standard-library
stabilization update plus the first language-usability diagnostics update and
the first local package/workspace discipline update plus the first loopback
networking foundation update plus the first serialization/data-interchange
foundation update. The beta baseline includes the completed `u32` / `u64`
unsigned compiler and stdlib breadth scope, the narrow `std.net` loopback TCP
runtime family, the narrow `std.json.quote_string` runtime family, and the
current ten-scaffold benchmark suite. This paper records the current beta
foundation update plus the first concrete type alias foundation. The beta
baseline includes the completed `u32` / `u64` unsigned compiler and stdlib
breadth scope, the narrow `std.net` loopback TCP runtime family, the narrow
`std.json.quote_string` runtime family, transparent concrete alias parsing and
erasure, and the current ten-scaffold benchmark suite. This paper records the
current beta
implementation surface, the benchmark method and results, the distinction
between Glagol and Lisp-family implementations, the beta.1 tooling update, the
beta.2 runtime/resource foundation, the beta.3 standard-library stabilization
slice, the beta.4 diagnostics usability slice, the beta.5 package discipline
slice, the beta.6 networking foundation slice, the beta.7 serialization
foundation slice, and the compiler path from beta to stable.
foundation slice, the beta.8 concrete alias foundation slice, and the compiler
path from beta to stable.
## 1. Compiler Thesis
@ -128,12 +132,14 @@ At the current technical behavior beta baseline, Glagol supports:
staged source-authored `std/*.slo` gates
- compact JSON string literal construction through `std.json.quote_string` and
the hosted `__glagol_json_quote_string` runtime helper
- transparent concrete type aliases erased before typed lowering, import
signature use, backend layout, ABI, and runtime behavior
- scalar C FFI imports
- benchmark scaffolds for Slovo, C, Rust, Python, Clojure, and Common
Lisp/SBCL, with `cold-process` and `hot-loop` timing modes
The current release, `1.0.0-beta.7`, is a beta serialization/data-interchange
foundation update on the first release line that may honestly use beta maturity
The current release, `1.0.0-beta.8`, is a beta concrete type alias foundation
update on the first release line that may honestly use beta maturity
language for this toolchain.
## 4. Diagnostics And Support Discipline
@ -303,10 +309,11 @@ baseline. `1.0.0-beta.1` changes tooling and install workflow, and
standard-library catalog and composition coverage, `1.0.0-beta.4` improves
diagnostics, `1.0.0-beta.5` tightens package/workspace discipline, and
`1.0.0-beta.6` adds a narrow loopback networking foundation, and
`1.0.0-beta.7` adds a narrow JSON construction foundation. None of these
post-beta slices claims changed benchmark performance. The beta.7
`json-quote-loop` scaffold is present for local follow-up timing and is not
part of the exp-123 nine-row result table below.
`1.0.0-beta.7` adds a narrow JSON construction foundation, and
`1.0.0-beta.8` adds transparent concrete type aliases. None of these post-beta
slices claims changed benchmark performance. The beta.7 `json-quote-loop`
scaffold is present for local follow-up timing and is not part of the exp-123
nine-row result table below.
The exp-123 publication baseline widened the paired same-machine result set
from seven rows to nine by adding two owned-vector kernels:
@ -394,15 +401,15 @@ coverage and compatibility:
- package behavior becoming stable before dependency, manifest, and versioning
rules are precise
## 9. Path Beyond `1.0.0-beta.7`
## 9. Path Beyond `1.0.0-beta.8`
Glagol now implements the first real beta Slovo contract, the first
post-beta tooling/install hardening release, the first runtime/resource
foundation release, the first standard-library stabilization release, and the
first diagnostics usability release, the first package/workspace discipline
release, the first loopback networking foundation release, and the first
serialization/data-interchange foundation release. The remaining path is from
beta to stable.
serialization/data-interchange foundation release, and the first concrete type
alias foundation release. The remaining path is from beta to stable.
Recommended compiler sequence:

Binary file not shown.

Binary file not shown.

View File

@ -5,7 +5,7 @@
Sanjin Gumbarevic<br>
hermeticum_lab@protonmail.com
Publication release: `1.0.0-beta.7`
Publication release: `1.0.0-beta.8`
Technical behavior baseline: language surface through `1.0.0-beta`; tooling
and install workflow through `1.0.0-beta.1`; runtime/resource foundation through
@ -13,12 +13,12 @@ and install workflow through `1.0.0-beta.1`; runtime/resource foundation through
language-usability diagnostics through `1.0.0-beta.4`; package/workspace
discipline through `1.0.0-beta.5`; loopback networking foundation through
`1.0.0-beta.6`; serialization/data-interchange foundation through
`1.0.0-beta.7`
`1.0.0-beta.7`; concrete type alias foundation through `1.0.0-beta.8`
Date: 2026-05-22
Evidence source: paired local Slovo/Glagol monorepo verification and benchmark
reruns from a local checkout; beta.7 release-gate verification from the public
reruns from a local checkout; beta.8 release-gate verification from the public
monorepo
Maturity: beta
@ -33,31 +33,34 @@ explicit types, explicit failure through `option` and `result`, lexical
`unsafe`, and native compilation through the Glagol compiler to LLVM IR and
hosted executables.
The current publication release, `1.0.0-beta.7`, keeps the first real
The current publication release, `1.0.0-beta.8`, keeps the first real
general-purpose beta language baseline from `1.0.0-beta` and records the first
post-beta tooling/install hardening update plus the first runtime/resource
foundation update, the first standard-library stabilization update, and the
first language-usability diagnostics update, plus the first local
package/workspace discipline update, the first loopback networking foundation
update, and the first serialization/data-interchange foundation update. The
beta baseline includes the completed `u32` / `u64`
update, the first serialization/data-interchange foundation update, and the
first concrete type alias foundation. The beta baseline includes the completed
`u32` / `u64`
unsigned scope, the staged stdlib breadth that makes ordinary command-line
programs practical, the current narrow `std.net` loopback TCP surface, the
current narrow `std.json` text-construction surface, and the current
ten-scaffold benchmark suite. This paper records the current beta
current narrow `std.json` text-construction surface, module-local transparent
aliases for supported concrete types, and the current ten-scaffold benchmark
suite. This paper records the current beta
technical state, the difference between Slovo and Lisp-family languages, the
benchmark methodology, the beta.1 tooling update, the beta.2 runtime/resource
foundation, the beta.3 standard-library stabilization slice, the beta.4
diagnostics usability slice, the beta.5 package discipline slice, the beta.6
networking foundation slice, the beta.7 serialization foundation slice, and
the remaining path from beta to stable.
the beta.8 concrete alias foundation slice, and the remaining path from beta
to stable.
## 1. Scope
This document is a technical state paper for the current beta baseline. It
summarizes the behavior represented by the paired local Slovo and Glagol
workspaces, with `1.0.0-beta` as the current language-surface baseline and
`1.0.0-beta.7` as the current publication baseline.
`1.0.0-beta.8` as the current publication baseline.
The support rule remains strict:
@ -69,7 +72,7 @@ The support rule remains strict:
- partial parser recognition or speculative examples do not count as support
Historical `exp-*` releases remain experimental alpha maturity. The current
publication accompanies `1.0.0-beta.7`.
publication accompanies `1.0.0-beta.8`.
## 2. Design Thesis
@ -380,10 +383,11 @@ baseline. `1.0.0-beta.1` changes tooling and install workflow, and
standard-library catalog and composition coverage, `1.0.0-beta.4` improves
diagnostics, `1.0.0-beta.5` tightens package/workspace discipline, and
`1.0.0-beta.6` adds a narrow loopback networking foundation, and
`1.0.0-beta.7` adds a narrow JSON construction foundation. None of these
post-beta slices claims changed benchmark performance. The beta.7
`json-quote-loop` scaffold is present for local follow-up timing and is not
part of the exp-123 nine-row result table below.
`1.0.0-beta.7` adds a narrow JSON construction foundation, and
`1.0.0-beta.8` adds transparent concrete type aliases. None of these post-beta
slices claims changed benchmark performance. The beta.7 `json-quote-loop`
scaffold is present for local follow-up timing and is not part of the exp-123
nine-row result table below.
The exp-123 publication baseline widened the paired same-machine result set
from seven rows to nine by adding two owned-vector kernels:
@ -504,7 +508,7 @@ Major remaining gaps before `1.0.0`:
- semantic versioning and deprecation policy
- a clear separation between stable and experimental features
## 10. Path Beyond `1.0.0-beta.7`
## 10. Path Beyond `1.0.0-beta.8`
The beta threshold is now real. The next work should treat `1.0.0-beta` as
the language compatibility-governed baseline, `1.0.0-beta.1` as the first
@ -514,7 +518,8 @@ stabilization point, and `1.0.0-beta.4` as the first diagnostics usability
point, and `1.0.0-beta.5` as the first package/workspace discipline point,
and `1.0.0-beta.6` as the first loopback networking foundation point, and
`1.0.0-beta.7` as the first serialization/data-interchange foundation point,
then move deliberately toward stable general-purpose status.
and `1.0.0-beta.8` as the first concrete type alias foundation point, then
move deliberately toward stable general-purpose status.
Recommended sequence:

Binary file not shown.

View File

@ -2,6 +2,10 @@
(import std.json (quote_string null_value bool_value i32_value u32_value i64_value u64_value f64_value field_string field_bool field_i32 field_u32 field_i64 field_u64 field_f64 field_null array0 array1 array2 array3 object0 object1 object2 object3))
(type JsonText string)
(type JsonField string)
(fn imported_json_quote_escapes () -> bool
(if (= (quote_string "slovo") "\"slovo\"")
(if (= (quote_string "slo\"vo") "\"slo\\\"vo\"")
@ -45,15 +49,21 @@
false)
false))
(fn imported_json_name_field () -> JsonField
(field_string "name" "slovo"))
(fn imported_json_object_document () -> JsonText
(object3 (imported_json_name_field) (field_i32 "count" 3) (field_bool "ok" true)))
(fn imported_json_arrays_objects () -> bool
(if (= (array0) "[]")
(if (= (array1 (quote_string "a")) "[\"a\"]")
(if (= (array2 (quote_string "a") (i32_value 7)) "[\"a\",7]")
(if (= (array3 (quote_string "a") (i32_value 7) (bool_value true)) "[\"a\",7,true]")
(if (= (object0) "{}")
(if (= (object1 (field_string "name" "slovo")) "{\"name\":\"slovo\"}")
(if (= (object2 (field_string "name" "slovo") (field_i32 "count" 3)) "{\"name\":\"slovo\",\"count\":3}")
(= (object3 (field_string "name" "slovo") (field_i32 "count" 3) (field_bool "ok" true)) "{\"name\":\"slovo\",\"count\":3,\"ok\":true}")
(if (= (object1 (imported_json_name_field)) "{\"name\":\"slovo\"}")
(if (= (object2 (imported_json_name_field) (field_i32 "count" 3)) "{\"name\":\"slovo\",\"count\":3}")
(= (imported_json_object_document) "{\"name\":\"slovo\",\"count\":3,\"ok\":true}")
false)
false)
false)

View File

@ -1,78 +1,82 @@
(module json (export quote_string null_value bool_value i32_value u32_value i64_value u64_value f64_value field_string field_bool field_i32 field_u32 field_i64 field_u64 field_f64 field_null array0 array1 array2 array3 object0 object1 object2 object3))
(fn quote_string ((value string)) -> string
(type JsonText string)
(type JsonField string)
(fn quote_string ((value string)) -> JsonText
(std.json.quote_string value))
(fn null_value () -> string
(fn null_value () -> JsonText
"null")
(fn bool_value ((value bool)) -> string
(fn bool_value ((value bool)) -> JsonText
(if value
"true"
"false"))
(fn i32_value ((value i32)) -> string
(fn i32_value ((value i32)) -> JsonText
(std.num.i32_to_string value))
(fn u32_value ((value u32)) -> string
(fn u32_value ((value u32)) -> JsonText
(std.num.u32_to_string value))
(fn i64_value ((value i64)) -> string
(fn i64_value ((value i64)) -> JsonText
(std.num.i64_to_string value))
(fn u64_value ((value u64)) -> string
(fn u64_value ((value u64)) -> JsonText
(std.num.u64_to_string value))
(fn f64_value ((value f64)) -> string
(fn f64_value ((value f64)) -> JsonText
(std.num.f64_to_string value))
(fn field_fragment ((name string) (encoded_value string)) -> string
(fn field_fragment ((name string) (encoded_value JsonText)) -> JsonField
(std.string.concat (std.string.concat (quote_string name) ":") encoded_value))
(fn field_string ((name string) (value string)) -> string
(fn field_string ((name string) (value string)) -> JsonField
(field_fragment name (quote_string value)))
(fn field_bool ((name string) (value bool)) -> string
(fn field_bool ((name string) (value bool)) -> JsonField
(field_fragment name (bool_value value)))
(fn field_i32 ((name string) (value i32)) -> string
(fn field_i32 ((name string) (value i32)) -> JsonField
(field_fragment name (i32_value value)))
(fn field_u32 ((name string) (value u32)) -> string
(fn field_u32 ((name string) (value u32)) -> JsonField
(field_fragment name (u32_value value)))
(fn field_i64 ((name string) (value i64)) -> string
(fn field_i64 ((name string) (value i64)) -> JsonField
(field_fragment name (i64_value value)))
(fn field_u64 ((name string) (value u64)) -> string
(fn field_u64 ((name string) (value u64)) -> JsonField
(field_fragment name (u64_value value)))
(fn field_f64 ((name string) (value f64)) -> string
(fn field_f64 ((name string) (value f64)) -> JsonField
(field_fragment name (f64_value value)))
(fn field_null ((name string)) -> string
(fn field_null ((name string)) -> JsonField
(field_fragment name (null_value)))
(fn array0 () -> string
(fn array0 () -> JsonText
"[]")
(fn array1 ((first string)) -> string
(fn array1 ((first JsonText)) -> JsonText
(std.string.concat (std.string.concat "[" first) "]"))
(fn array2 ((first string) (second string)) -> string
(fn array2 ((first JsonText) (second JsonText)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat "[" first) ",") second) "]"))
(fn array3 ((first string) (second string) (third string)) -> string
(fn array3 ((first JsonText) (second JsonText) (third JsonText)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat "[" first) ",") second) ",") third) "]"))
(fn object0 () -> string
(fn object0 () -> JsonText
"{}")
(fn object1 ((first string)) -> string
(fn object1 ((first JsonField)) -> JsonText
(std.string.concat (std.string.concat "{" first) "}"))
(fn object2 ((first string) (second string)) -> string
(fn object2 ((first JsonField) (second JsonField)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat "{" first) ",") second) "}"))
(fn object3 ((first string) (second string) (third string)) -> string
(fn object3 ((first JsonField) (second JsonField) (third JsonField)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat "{" first) ",") second) ",") third) "}"))

View File

@ -2,6 +2,10 @@
(import json (quote_string null_value bool_value i32_value u32_value i64_value u64_value f64_value field_string field_bool field_i32 field_u32 field_i64 field_u64 field_f64 field_null array0 array1 array2 array3 object0 object1 object2 object3))
(type JsonText string)
(type JsonField string)
(fn imported_json_quote_escapes () -> bool
(if (= (quote_string "slovo") "\"slovo\"")
(if (= (quote_string "slo\"vo") "\"slo\\\"vo\"")
@ -45,15 +49,21 @@
false)
false))
(fn imported_json_name_field () -> JsonField
(field_string "name" "slovo"))
(fn imported_json_object_document () -> JsonText
(object3 (imported_json_name_field) (field_i32 "count" 3) (field_bool "ok" true)))
(fn imported_json_arrays_objects () -> bool
(if (= (array0) "[]")
(if (= (array1 (quote_string "a")) "[\"a\"]")
(if (= (array2 (quote_string "a") (i32_value 7)) "[\"a\",7]")
(if (= (array3 (quote_string "a") (i32_value 7) (bool_value true)) "[\"a\",7,true]")
(if (= (object0) "{}")
(if (= (object1 (field_string "name" "slovo")) "{\"name\":\"slovo\"}")
(if (= (object2 (field_string "name" "slovo") (field_i32 "count" 3)) "{\"name\":\"slovo\",\"count\":3}")
(= (object3 (field_string "name" "slovo") (field_i32 "count" 3) (field_bool "ok" true)) "{\"name\":\"slovo\",\"count\":3,\"ok\":true}")
(if (= (object1 (imported_json_name_field)) "{\"name\":\"slovo\"}")
(if (= (object2 (imported_json_name_field) (field_i32 "count" 3)) "{\"name\":\"slovo\",\"count\":3}")
(= (imported_json_object_document) "{\"name\":\"slovo\",\"count\":3,\"ok\":true}")
false)
false)
false)

View File

@ -35,7 +35,10 @@ prefix/suffix helper scopes; `1.0.0-beta.6` networking foundation work releases
`std/net.slo` as an experimental loopback TCP facade over matching
compiler-known runtime calls; `1.0.0-beta.7` serialization work releases
`std/json.slo` as an experimental JSON text-construction facade over
`std.json.quote_string` and existing string/number helpers.
`std.json.quote_string` and existing string/number helpers; the
`1.0.0-beta.8` concrete type alias target keeps that same helper surface while
using local `JsonText` and `JsonField` aliases as transparent names for
`string` JSON fragments.
This directory is the source home for staged standard library modules and
examples. exp-44 lets project-mode source explicitly import `std/math.slo` as
@ -99,6 +102,11 @@ text-construction facade over `std.json.quote_string`, `std.string.concat`,
and the current `std.num.*_to_string` helpers, while leaving JSON parsing,
recursive JSON values, maps/sets, streaming encoders, schema validation,
Unicode normalization, and stable text encoding policy deferred;
`1.0.0-beta.8` targets top-level concrete type aliases in source facades and
uses `JsonText` / `JsonField` only as local transparent aliases inside
`std/json.slo` and matching local JSON fixtures. These aliases are not exported
standard-library names and do not add runtime helpers, generic aliases,
parameterized aliases, maps/sets, or ABI/layout guarantees;
exp-76 extends project-mode source search to `std/vec_i32.slo`, a concrete
source-authored collection facade over the current promoted `std.vec.i32`
runtime family; exp-77 extends that facade with concrete option-returning

View File

@ -1,78 +1,82 @@
(module json (export quote_string null_value bool_value i32_value u32_value i64_value u64_value f64_value field_string field_bool field_i32 field_u32 field_i64 field_u64 field_f64 field_null array0 array1 array2 array3 object0 object1 object2 object3))
(fn quote_string ((value string)) -> string
(type JsonText string)
(type JsonField string)
(fn quote_string ((value string)) -> JsonText
(std.json.quote_string value))
(fn null_value () -> string
(fn null_value () -> JsonText
"null")
(fn bool_value ((value bool)) -> string
(fn bool_value ((value bool)) -> JsonText
(if value
"true"
"false"))
(fn i32_value ((value i32)) -> string
(fn i32_value ((value i32)) -> JsonText
(std.num.i32_to_string value))
(fn u32_value ((value u32)) -> string
(fn u32_value ((value u32)) -> JsonText
(std.num.u32_to_string value))
(fn i64_value ((value i64)) -> string
(fn i64_value ((value i64)) -> JsonText
(std.num.i64_to_string value))
(fn u64_value ((value u64)) -> string
(fn u64_value ((value u64)) -> JsonText
(std.num.u64_to_string value))
(fn f64_value ((value f64)) -> string
(fn f64_value ((value f64)) -> JsonText
(std.num.f64_to_string value))
(fn field_fragment ((name string) (encoded_value string)) -> string
(fn field_fragment ((name string) (encoded_value JsonText)) -> JsonField
(std.string.concat (std.string.concat (quote_string name) ":") encoded_value))
(fn field_string ((name string) (value string)) -> string
(fn field_string ((name string) (value string)) -> JsonField
(field_fragment name (quote_string value)))
(fn field_bool ((name string) (value bool)) -> string
(fn field_bool ((name string) (value bool)) -> JsonField
(field_fragment name (bool_value value)))
(fn field_i32 ((name string) (value i32)) -> string
(fn field_i32 ((name string) (value i32)) -> JsonField
(field_fragment name (i32_value value)))
(fn field_u32 ((name string) (value u32)) -> string
(fn field_u32 ((name string) (value u32)) -> JsonField
(field_fragment name (u32_value value)))
(fn field_i64 ((name string) (value i64)) -> string
(fn field_i64 ((name string) (value i64)) -> JsonField
(field_fragment name (i64_value value)))
(fn field_u64 ((name string) (value u64)) -> string
(fn field_u64 ((name string) (value u64)) -> JsonField
(field_fragment name (u64_value value)))
(fn field_f64 ((name string) (value f64)) -> string
(fn field_f64 ((name string) (value f64)) -> JsonField
(field_fragment name (f64_value value)))
(fn field_null ((name string)) -> string
(fn field_null ((name string)) -> JsonField
(field_fragment name (null_value)))
(fn array0 () -> string
(fn array0 () -> JsonText
"[]")
(fn array1 ((first string)) -> string
(fn array1 ((first JsonText)) -> JsonText
(std.string.concat (std.string.concat "[" first) "]"))
(fn array2 ((first string) (second string)) -> string
(fn array2 ((first JsonText) (second JsonText)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat "[" first) ",") second) "]"))
(fn array3 ((first string) (second string) (third string)) -> string
(fn array3 ((first JsonText) (second JsonText) (third JsonText)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat "[" first) ",") second) ",") third) "]"))
(fn object0 () -> string
(fn object0 () -> JsonText
"{}")
(fn object1 ((first string)) -> string
(fn object1 ((first JsonField)) -> JsonText
(std.string.concat (std.string.concat "{" first) "}"))
(fn object2 ((first string) (second string)) -> string
(fn object2 ((first JsonField) (second JsonField)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat "{" first) ",") second) "}"))
(fn object3 ((first string) (second string) (third string)) -> string
(fn object3 ((first JsonField) (second JsonField) (third JsonField)) -> JsonText
(std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat (std.string.concat "{" first) ",") second) ",") third) "}"))

View File

@ -0,0 +1,21 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code TypeAliasCycle)
(message "type alias cycle includes `A`")
(file "<fixture>")
(span
(bytes 22 23)
(range 4 7 4 8)
)
(hint "type aliases must resolve to an existing non-alias concrete type")
(related
(span
(file "<fixture>")
(bytes 33 34)
(range 5 7 5 8)
(message "cycle also includes `B`")
)
)
)

View File

@ -0,0 +1,20 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code DuplicateTypeAlias)
(message "duplicate type alias `Count`")
(file "<fixture>")
(span
(bytes 39 44)
(range 5 7 5 12)
)
(related
(span
(file "<fixture>")
(bytes 22 27)
(range 4 7 4 12)
(message "original type alias")
)
)
)

View File

@ -0,0 +1,13 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code MalformedTypeAlias)
(message "type alias form must be `(type Alias TargetType)`")
(file "<fixture>")
(span
(bytes 16 28)
(range 4 1 4 13)
)
(expected "(type Alias TargetType)")
)

View File

@ -0,0 +1,13 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code SelfTypeAlias)
(message "type alias `Count` directly aliases itself")
(file "<fixture>")
(span
(bytes 28 33)
(range 4 13 4 18)
)
(hint "point the alias at an existing concrete type")
)

View File

@ -0,0 +1,20 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code TypeAliasNameConflict)
(message "type alias `Count` conflicts with a struct")
(file "<fixture>")
(span
(bytes 52 57)
(range 7 7 7 12)
)
(related
(span
(file "<fixture>")
(bytes 24 29)
(range 4 9 4 14)
(message "struct declaration")
)
)
)

View File

@ -0,0 +1,41 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code TypeAliasNameConflict)
(message "type alias `main` conflicts with a function")
(file "<fixture>")
(span
(bytes 61 65)
(range 6 7 6 11)
)
(related
(span
(file "<fixture>")
(bytes 90 113)
(range 10 1 11 5)
(message "function declaration")
)
)
)
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code TypeAliasNameConflict)
(message "type alias `c_add` conflicts with a C import")
(file "<fixture>")
(span
(bytes 78 83)
(range 8 7 8 12)
)
(related
(span
(file "<fixture>")
(bytes 26 31)
(range 4 11 4 16)
(message "C import declaration")
)
)
)

View File

@ -0,0 +1,97 @@
program main
enum Reading
variant Empty
variant Value i32
struct Measurement
field amount: i32
field label: string
fn bump(value: i32) -> i32
binary + : i32
var value : i32
int 1 : i32
fn make_counts(base: i32) -> (array i32 3)
array : (array i32 3)
var base : i32
binary + : i32
var base : i32
int 1 : i32
binary + : i32
var base : i32
int 2 : i32
fn pick_count(values: (array i32 3)) -> i32
index : i32
var values : (array i32 3)
int 1 : i32
fn make_measure(amount: i32) -> Measurement
construct Measurement : Measurement
field amount
var amount : i32
field label
string "ok" : string
fn measure_amount(measure: Measurement) -> i32
field-access amount : i32
var measure : Measurement
fn maybe_amount(amount: i32) -> (option i32)
some : (option i32)
var amount : i32
fn maybe_empty() -> (option i32)
none : (option i32)
fn ok_amount(amount: i32) -> (result i32 i32)
ok : (result i32 i32)
var amount : i32
fn err_amount(code: i32) -> (result i32 i32)
err : (result i32 i32)
var code : i32
fn empty_counts() -> (vec i32)
call std.vec.i32.empty : (vec i32)
fn reading_value(amount: i32) -> Reading
enum-variant Reading.Value #1 payload : Reading
var amount : i32
fn reading_score(reading: Reading) -> i32
match : i32
subject
var reading : Reading
arm Reading.Empty
int 0 : i32
arm Reading.Value amount
var amount : i32
fn main() -> i32
local let values : unit
call make_counts : (array i32 3)
int 40 : i32
binary + : i32
call pick_count : i32
var values : (array i32 3)
call measure_amount : i32
call make_measure : Measurement
int 1 : i32
test "aliases erase through arrays and structs"
binary = : bool
call main : i32
int 42 : i32
test "aliases erase through option result vec and enum"
local let maybe : unit
call maybe_amount : (option i32)
int 7 : i32
local let result : unit
call ok_amount : (result i32 i32)
int 8 : i32
if : bool
is_some : bool
var maybe : (option i32)
if : bool
is_ok : bool
var result : (result i32 i32)
if : bool
binary = : bool
call std.vec.i32.len : i32
call empty_counts : (vec i32)
int 0 : i32
binary = : bool
call reading_score : i32
call reading_value : Reading
int 9 : i32
int 9 : i32
bool false : bool
bool false : bool
bool false : bool

79
tests/type-aliases.slo Normal file
View File

@ -0,0 +1,79 @@
(module main)
(type Count i32)
(type Score Count)
(type Text string)
(type Counts (array Count 3))
(type CountVec (vec Count))
(type MaybeCount (option Count))
(type CountResult (result Count Count))
(struct Measurement
(amount Count)
(label Text))
(type Measure Measurement)
(enum Reading
Empty
(Value Count))
(type ReadingAlias Reading)
(fn bump ((value Count)) -> Score
(+ value 1))
(fn make_counts ((base Count)) -> Counts
(array Count base (+ base 1) (+ base 2)))
(fn pick_count ((values Counts)) -> Count
(index values 1))
(fn make_measure ((amount Count)) -> Measure
(Measurement (amount amount) (label "ok")))
(fn measure_amount ((measure Measure)) -> Count
(. measure amount))
(fn maybe_amount ((amount Count)) -> MaybeCount
(some Count amount))
(fn maybe_empty () -> MaybeCount
(none Count))
(fn ok_amount ((amount Count)) -> CountResult
(ok Count Count amount))
(fn err_amount ((code Count)) -> CountResult
(err Count Count code))
(fn empty_counts () -> CountVec
(std.vec.i32.empty))
(fn reading_value ((amount Count)) -> ReadingAlias
(Reading.Value amount))
(fn reading_score ((reading ReadingAlias)) -> Count
(match reading
((Reading.Empty)
0)
((Reading.Value amount)
amount)))
(fn main () -> i32
(let values Counts (make_counts 40))
(+ (pick_count values) (measure_amount (make_measure 1))))
(test "aliases erase through arrays and structs"
(= (main) 42))
(test "aliases erase through option result vec and enum"
(let maybe MaybeCount (maybe_amount 7))
(let result CountResult (ok_amount 8))
(and (is_some maybe) (and (is_ok result) (and (= (std.vec.i32.len (empty_counts)) 0) (= (reading_score (reading_value 9)) 9)))))

View File

@ -0,0 +1,106 @@
program main
type Count = i32
type Score = Count
type Text = string
type Counts = (array Count 3)
type CountVec = (vec Count)
type MaybeCount = (option Count)
type CountResult = (result Count Count)
type Measure = Measurement
type ReadingAlias = Reading
enum Reading
variant Empty
variant Value Count
struct Measurement
field amount: Count
field label: Text
fn bump(value: Count) -> Score
binary +
var value
int 1
fn make_counts(base: Count) -> Counts
array Count
var base
binary +
var base
int 1
binary +
var base
int 2
fn pick_count(values: Counts) -> Count
index
var values
int 1
fn make_measure(amount: Count) -> Measure
construct Measurement
field amount
var amount
field label
string "ok"
fn measure_amount(measure: Measure) -> Count
field-access amount
var measure
fn maybe_amount(amount: Count) -> MaybeCount
some Count
var amount
fn maybe_empty() -> MaybeCount
none Count
fn ok_amount(amount: Count) -> CountResult
ok Count Count
var amount
fn err_amount(code: Count) -> CountResult
err Count Count
var code
fn empty_counts() -> CountVec
call std.vec.i32.empty
fn reading_value(amount: Count) -> ReadingAlias
enum-variant Reading.Value
var amount
fn reading_score(reading: ReadingAlias) -> Count
match
subject
var reading
arm Reading.Empty
int 0
arm Reading.Value amount
var amount
fn main() -> i32
local let values: Counts
call make_counts
int 40
binary +
call pick_count
var values
call measure_amount
call make_measure
int 1
test "aliases erase through arrays and structs"
binary =
call main
int 42
test "aliases erase through option result vec and enum"
local let maybe: MaybeCount
call maybe_amount
int 7
local let result: CountResult
call ok_amount
int 8
if
is_some
var maybe
if
is_ok
var result
if
binary =
call std.vec.i32.len
call empty_counts
int 0
binary =
call reading_score
call reading_value
int 9
int 9
bool false
bool false
bool false

View File

@ -0,0 +1,15 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code UnknownTypeAliasTarget)
(message "type alias target `Missing` is not a known concrete type")
(file "<fixture>")
(span
(bytes 28 35)
(range 4 13 4 20)
)
(expected "built-in type, known struct, known enum, or known type alias")
(found "Missing")
(hint "declare the target type or alias before checking this alias set")
)

View File

@ -0,0 +1,79 @@
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code UnsupportedTypeAliasTarget)
(message "type alias target type is not supported in beta.8")
(file "<fixture>")
(span
(bytes 50 59)
(range 6 14 6 23)
)
(expected "i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
(found "(ptr i32)")
(hint "aliases are transparent and may only target concrete types already supported in the target use positions")
)
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code UnsupportedTypeAliasTarget)
(message "type alias target type is not supported in beta.8")
(file "<fixture>")
(span
(bytes 132 151)
(range 12 17 12 36)
)
(expected "i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
(found "(result i32 string)")
(hint "aliases are transparent and may only target concrete types already supported in the target use positions")
)
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code UnsupportedTypeAliasTarget)
(message "type alias target type is not supported in beta.8")
(file "<fixture>")
(span
(bytes 77 88)
(range 8 16 8 27)
)
(expected "i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
(found "(slice i32)")
(hint "aliases are transparent and may only target concrete types already supported in the target use positions")
)
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code UnsupportedTypeAliasTarget)
(message "type alias target type is not supported in beta.8")
(file "<fixture>")
(span
(bytes 30 34)
(range 4 15 4 19)
)
(expected "i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
(found "unit")
(hint "aliases are transparent and may only target concrete types already supported in the target use positions")
)
(diagnostic
(schema slovo.diagnostic)
(version 1)
(severity error)
(code UnsupportedTypeAliasTarget)
(message "type alias target type is not supported in beta.8")
(file "<fixture>")
(span
(bytes 104 113)
(range 10 14 10 23)
)
(expected "i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
(found "(vec u32)")
(hint "aliases are transparent and may only target concrete types already supported in the target use positions")
)