Release 1.0.0-beta.8 concrete type aliases
This commit is contained in:
parent
be6cdfb87c
commit
4f52a54bea
60
.llm/BETA_8_CONCRETE_TYPE_ALIASES.md
Normal file
60
.llm/BETA_8_CONCRETE_TYPE_ALIASES.md
Normal 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.
|
||||
@ -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.
|
||||
|
||||
52
.llm/reviews/BETA_8_RELEASE_REVIEW.md
Normal file
52
.llm/reviews/BETA_8_RELEASE_REVIEW.md
Normal 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.
|
||||
30
README.md
30
README.md
@ -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
2
compiler/Cargo.lock
generated
@ -4,4 +4,4 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "glagol"
|
||||
version = "1.0.0-beta.7"
|
||||
version = "1.0.0-beta.8"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(¶m.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(¶m.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,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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,
|
||||
|
||||
116
compiler/tests/concrete_type_aliases_beta.rs
Normal file
116
compiler/tests/concrete_type_aliases_beta.rs
Normal 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);
|
||||
}
|
||||
@ -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#"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
|
||||
79
docs/language/examples/formatter/type-aliases.slo
Normal file
79
docs/language/examples/formatter/type-aliases.slo
Normal 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)))))
|
||||
Binary file not shown.
@ -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.
@ -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.
@ -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)
|
||||
|
||||
@ -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) "}"))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) "}"))
|
||||
|
||||
21
tests/cyclic-type-alias.diag
Normal file
21
tests/cyclic-type-alias.diag
Normal 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`")
|
||||
)
|
||||
)
|
||||
)
|
||||
20
tests/duplicate-type-alias.diag
Normal file
20
tests/duplicate-type-alias.diag
Normal 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")
|
||||
)
|
||||
)
|
||||
)
|
||||
13
tests/malformed-type-alias.diag
Normal file
13
tests/malformed-type-alias.diag
Normal 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)")
|
||||
)
|
||||
13
tests/self-type-alias.diag
Normal file
13
tests/self-type-alias.diag
Normal 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")
|
||||
)
|
||||
20
tests/type-alias-name-conflict.diag
Normal file
20
tests/type-alias-name-conflict.diag
Normal 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")
|
||||
)
|
||||
)
|
||||
)
|
||||
41
tests/type-alias-value-name-conflict.diag
Normal file
41
tests/type-alias-value-name-conflict.diag
Normal 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")
|
||||
)
|
||||
)
|
||||
)
|
||||
97
tests/type-aliases.checked.lower
Normal file
97
tests/type-aliases.checked.lower
Normal 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
79
tests/type-aliases.slo
Normal 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)))))
|
||||
106
tests/type-aliases.surface.lower
Normal file
106
tests/type-aliases.surface.lower
Normal 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
|
||||
15
tests/unknown-type-alias-target.diag
Normal file
15
tests/unknown-type-alias-target.diag
Normal 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")
|
||||
)
|
||||
79
tests/unsupported-type-alias-target.diag
Normal file
79
tests/unsupported-type-alias-target.diag
Normal 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")
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user