From 87f90ba264cd34b88df64505ccf4ab55a2485802 Mon Sep 17 00:00:00 2001 From: sanjin Date: Fri, 22 May 2026 20:24:42 +0200 Subject: [PATCH] Release 1.0.0-beta.11 local package api documentation --- ...BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md | 57 +++ .llm/reviews/BETA_11_RELEASE_REVIEW.md | 73 +++ README.md | 52 ++- compiler/Cargo.lock | 2 +- compiler/Cargo.toml | 2 +- compiler/src/docgen.rs | 339 +++++++++++++- compiler/tests/doc_api_beta11.rs | 420 ++++++++++++++++++ docs/POST_BETA_ROADMAP.md | 38 +- docs/compiler/RELEASE_NOTES.md | 35 +- docs/compiler/ROADMAP.md | 20 +- docs/language/RELEASE_NOTES.md | 38 +- docs/language/ROADMAP.md | 26 +- docs/language/SPEC-v1.md | 95 +++- docs/language/STDLIB_API.md | 2 +- 14 files changed, 1147 insertions(+), 52 deletions(-) create mode 100644 .llm/BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md create mode 100644 .llm/reviews/BETA_11_RELEASE_REVIEW.md create mode 100644 compiler/tests/doc_api_beta11.rs diff --git a/.llm/BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md b/.llm/BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md new file mode 100644 index 0000000..c29a682 --- /dev/null +++ b/.llm/BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md @@ -0,0 +1,57 @@ +# 1.0.0-beta.11 Local Package API Documentation + +Status: release scope for `1.0.0-beta.11`. + +`1.0.0-beta.11` extends the beta.10 API discovery lane. The release keeps the +`1.0.0-beta` source-language and runtime baseline unchanged while making local +package and module documentation show the public API surface users need to +review. + +## Scope + +- Extend `glagol doc -o ` so generated Markdown + includes deterministic exported/public API sections for local source files, + projects, packages, and workspaces. +- Render exact exported function signatures with parameter names, parameter + types, and return types. +- Render exported struct field names and field types. +- Render exported enum variant names and payload types for payloadless and + current single-payload variants. +- Keep non-exported functions, structs, enums, tests, and `(type ...)` aliases + out of the public API sections. +- Normalize module-local concrete aliases before rendering public types, so + private names such as `VecI32`, `OptionString`, or `ResultU64` do not leak + into local package/module public docs. +- Update README, language docs, compiler docs, and the post-beta roadmap to + describe the beta11 documentation contract clearly. + +## Public Contract + +The generated local documentation is a beta API discovery aid. It exposes what +the current local module/package export lists make public, with concrete public +types after alias normalization. + +The public API sections are deterministic and suitable for human review, but +they are not a stable machine-readable Markdown schema. Headings, anchors, file +names, and surrounding prose remain beta-scoped unless a later release freezes +them explicitly. + +## Explicit Non-Scope + +- no stable Markdown schema +- no stable stdlib/API compatibility freeze +- no LSP server or watch mode +- no SARIF or daemon protocol +- no diagnostics schema policy +- no executable generics +- no maps or sets +- no re-exports, glob imports, or hierarchical modules +- no package registry semantics +- no new compiler-known runtime names +- no runtime helper or ABI/layout changes + +## Checks + +Focused checks for this slice: + +- `git diff --check -- README.md docs/POST_BETA_ROADMAP.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md docs/language/SPEC-v1.md docs/compiler/ROADMAP.md docs/compiler/RELEASE_NOTES.md .llm/BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md` diff --git a/.llm/reviews/BETA_11_RELEASE_REVIEW.md b/.llm/reviews/BETA_11_RELEASE_REVIEW.md new file mode 100644 index 0000000..e84bb93 --- /dev/null +++ b/.llm/reviews/BETA_11_RELEASE_REVIEW.md @@ -0,0 +1,73 @@ +# 1.0.0-beta.11 Release Review + +Status: ready for publication after the controller release gate. + +## Verdict + +No blocking issues remain after controller follow-up. The original workspace +package API leak was reproduced, fixed, and covered by a regression test before +publication. + +## Resolved Finding + +- Workspace package API docs originally included compiler-loaded standard + library modules when a workspace package imported `std.option`. +- Controller fix: workspace package API rendering now filters package modules + to the package source root before collecting public API sections, matching + the existing project-mode local-root behavior. Ordinary module summaries can + still show loaded standard-library modules, but package API sections no + longer present those modules as package-owned API. +- Regression added: + `doc_workspace_package_api_excludes_loaded_std_modules`. + +## Scope Checked + +- `compiler/src/docgen.rs` adds per-module public API sections and package API + sections, renders exported function signatures, struct fields, enum variants, + and normalizes module-local aliases before public rendering. +- `compiler/tests/doc_api_beta11.rs` covers file, project, workspace scaffold, + workspace packages importing `std.*`, alias normalization, non-exported + function omission, and byte-identical repeat generation for a file input. +- README, compiler/language roadmaps, release notes, SPEC-v1, `STDLIB_API.md`, + and the beta11 `.llm` contract include the main deferrals: no stable Markdown + schema, no stable stdlib/API compatibility freeze, no LSP/watch, no + SARIF/daemon protocol, no diagnostics schema policy, no executable generics, + no maps/sets, no re-exports/globs/hierarchical modules, and no registry + semantics. +- Version bumps in `compiler/Cargo.toml` and `compiler/Cargo.lock` move + `glagol` from `1.0.0-beta.10` to `1.0.0-beta.11`. + +## Test Coverage Notes + +- Workspace packages that import `std.*` are now covered by a focused + regression that verifies package API excludes loaded standard-library + modules and helpers. +- Determinism is asserted byte-for-byte only for file docs. Project and + workspace determinism are indirectly exercised by sorted rendering but not + protected by repeat-generation tests. +- Non-export filtering is asserted for functions and aliases. There is no + explicit negative test for non-exported structs, non-exported enums, or tests + appearing in public API sections. +- Alias normalization is covered for file/module docs, but not for project or + workspace package API fixtures. + +## Verification + +- `cargo fmt --check`: passed. +- `cargo check`: passed. +- `cargo test --test doc_api_beta11`: passed, 6 tests. +- `cargo test --test dx_v1_7`: passed, 13 tests. +- `cargo test --test symbols_beta10`: passed, 4 tests. +- `cargo test --test promotion_gate`: passed, 1 unignored test. +- `git diff --check`: passed. +- Stale beta10 current-release/version scan over README, docs, `.llm`, and + compiler package metadata: no matches. +- Private/local publication text scan over README, docs, scripts, compiler + source/tests, lib, examples, benchmarks, tests, and `.llm`: no matches. +- `node scripts/render-stdlib-api-doc.js`: passed and refreshed the catalog + release wording to `1.0.0-beta.11`. +- Manual workspace std-import doc generation after the fix: package API no + longer includes loaded standard-library modules. +- `./scripts/release-gate.sh`: passed docs, generated stdlib API catalog + consistency, private-publication text checks, formatter checks, the full cargo + test suite, ignored promotion checks, binary smoke, and LLVM smoke. diff --git a/README.md b/README.md index 85ca828..262e770 100644 --- a/README.md +++ b/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.10`. +Current release: `1.0.0-beta.11`. ## Repository Layout @@ -24,16 +24,17 @@ scripts/ local release and document tooling ## Beta Scope -`1.0.0-beta.10` keeps the `1.0.0-beta` language baseline, includes the +`1.0.0-beta.11` 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 and the `1.0.0-beta.8` concrete type -alias foundation, and the `1.0.0-beta.9` collection alias unification and -generic reservation slice, plus the `1.0.0-beta.10` developer-experience API -discovery slice. The language baseline supports practical local +alias foundation, the `1.0.0-beta.9` collection alias unification and +generic reservation slice, the `1.0.0-beta.10` developer-experience API +discovery slice, and the `1.0.0-beta.11` local package API documentation +slice. The language baseline supports practical local command-line, file, and loopback-network programs with: - modules, explicit imports, packages, and local workspaces @@ -56,17 +57,24 @@ types, and omits non-exported helpers and `(type ...)` aliases. `glagol symbols ` emits deterministic editor-facing S-expression metadata for modules, imports, exports, aliases, structs, enums, functions, tests, source spans, and workspace package names. +`glagol doc -o ` now includes deterministic public +API sections for local package and module documentation: exact exported +function signatures, exported struct fields, exported enum variants and payload +types, non-export filtering, and module-local alias normalization. Still deferred before stable: executable generics, maps/sets, broad package -registry semantics, DNS/TLS/async networking, LSP/watch/debug-adapter -guarantees, stable ABI and layout, runtime changes for generic collections, -and a stable standard-library compatibility freeze. +registry semantics, stable Markdown schema, stable stdlib/API compatibility +freeze, DNS/TLS/async networking, LSP/watch guarantees, SARIF and daemon +protocols, diagnostics schema policy, re-exports/globs/hierarchical modules, +stable ABI and layout, and runtime changes for generic collections. -The next likely language slice after `1.0.0-beta.10` should continue from the -developer-experience and reserved generic/collection diagnostics without -claiming executable generics, maps, sets, traits, inference, -monomorphization, iterators, ABI stability, runtime changes, or a -standard-library API freeze until the contract and gates are explicit. +The next likely language slice after `1.0.0-beta.11` should continue from the +developer-experience, diagnostics, and reserved generic/collection lanes +without claiming executable generics, maps, sets, traits, inference, +monomorphization, iterators, ABI stability, runtime changes, LSP/watch +protocols, registry semantics, stable Markdown schema, or a +standard-library/API compatibility freeze until the contract and gates are +explicit. ## Build And Test @@ -261,6 +269,24 @@ add executable generics, maps, sets, new runtime helpers, new compiler-known runtime names, ABI/layout guarantees, an LSP server, watch mode, or a stable standard-library API freeze. +## 1.0.0-beta.11 Local Package API Documentation + +The `1.0.0-beta.11` release extends the beta.10 API discovery lane to local +package and module documentation. `glagol doc -o ` +includes deterministic exported/public API sections for local modules and +workspace packages. + +Those sections list exact exported function signatures, exported struct fields, +and exported enum variants with payload types. They omit non-exported +functions, structs, enums, tests, and aliases from the public API surface, and +they normalize module-local concrete aliases before rendering public types. + +This remains beta API discovery. It does not freeze the Markdown schema, create +a stable stdlib/API compatibility freeze, add LSP/watch behavior, define +SARIF or daemon protocols, set diagnostics schema policy, implement executable +generics, maps, or sets, add re-exports, globs, or hierarchical modules, or +define package registry semantics. + ## Documentation - [Language Manifest](docs/language/MANIFEST.md) diff --git a/compiler/Cargo.lock b/compiler/Cargo.lock index 3ee538c..8463c80 100644 --- a/compiler/Cargo.lock +++ b/compiler/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "glagol" -version = "1.0.0-beta.10" +version = "1.0.0-beta.11" diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 1535702..9c525b1 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "glagol" -version = "1.0.0-beta.10" +version = "1.0.0-beta.11" edition = "2021" description = "Glagol, the first compiler for the Slovo language" license = "MIT OR Apache-2.0" diff --git a/compiler/src/docgen.rs b/compiler/src/docgen.rs index eac7dab..3ea567c 100644 --- a/compiler/src/docgen.rs +++ b/compiler/src/docgen.rs @@ -1,4 +1,8 @@ -use std::{fs, path::Path}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, + path::Path, +}; use crate::{ ast::Program, @@ -6,6 +10,7 @@ use crate::{ lexer, lower, project::{self, ProjectArtifact, SourceFile, ToolFailure, WorkspaceArtifact}, sexpr::{Atom, SExpr, SExprKind}, + types::Type, }; pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> { @@ -56,16 +61,23 @@ fn render_project( artifact: &ProjectArtifact, sources: &[SourceFile], ) -> Result { + let modules = sources + .iter() + .map(document_source) + .collect::, _>>()?; + let mut out = String::new(); out.push_str("# Project "); out.push_str(&artifact.project_name); out.push_str("\n\n"); if let Some(workspace) = &artifact.workspace { render_workspace(&mut out, workspace); + render_workspace_package_public_api(&mut out, workspace, &modules); + } else { + render_project_package_public_api(&mut out, artifact, &modules); } - for source in sources { - let module = document_source(source)?; - render_module(&mut out, &module); + for module in &modules { + render_module(&mut out, module); } Ok(out) } @@ -120,27 +132,58 @@ fn document_source(source: &SourceFile) -> Result { sources: vec![source.clone()], artifact: None, })?; - let program = lower::lower_program(&source.path, &forms).ok(); + let lowerable_forms = forms + .iter() + .filter(|form| !matches!(list_head(form), Some("import"))) + .cloned() + .collect::>(); + let program = lower::lower_program(&source.path, &lowerable_forms).ok(); Ok(module_from_forms(&source.path, &forms, program.as_ref())) } struct DocModule { + path: String, title: String, imports: Vec, exports: Vec, structs: Vec, functions: Vec, tests: Vec, + public_api: PublicApi, +} + +#[derive(Default)] +struct PublicApi { + functions: Vec, + structs: Vec, + enums: Vec, +} + +struct DocFunction { + name: String, + signature: String, +} + +struct DocStruct { + name: String, + fields: Vec, +} + +struct DocEnum { + name: String, + variants: Vec, } fn module_from_forms(file: &str, forms: &[SExpr], program: Option<&Program>) -> DocModule { let mut module = DocModule { + path: file.to_string(), title: file.to_string(), imports: Vec::new(), exports: Vec::new(), structs: Vec::new(), functions: Vec::new(), tests: Vec::new(), + public_api: PublicApi::default(), }; for form in forms { @@ -205,6 +248,7 @@ fn module_from_forms(file: &str, forms: &[SExpr], program: Option<&Program>) -> }) .collect(); module.tests = program.tests.iter().map(|test| test.name.clone()).collect(); + module.public_api = public_api_from_program(program, &module.exports); } module.imports.sort(); @@ -221,11 +265,120 @@ fn render_module(out: &mut String, module: &DocModule) { out.push_str("\n\n"); render_list(out, "Imports", &module.imports); render_list(out, "Exports", &module.exports); + render_public_api_section(out, "###", &module.public_api); render_list(out, "Structs", &module.structs); render_list(out, "Functions", &module.functions); render_list(out, "Tests", &module.tests); } +fn render_project_package_public_api( + out: &mut String, + artifact: &ProjectArtifact, + modules: &[DocModule], +) { + let local_root = Path::new(&artifact.project_root).join(&artifact.source_root); + let local_modules = modules + .iter() + .filter(|module| Path::new(&module.path).starts_with(&local_root)) + .collect::>(); + render_package_public_api(out, &artifact.project_name, &local_modules); +} + +fn render_workspace_package_public_api( + out: &mut String, + workspace: &WorkspaceArtifact, + modules: &[DocModule], +) { + let by_path = modules + .iter() + .map(|module| (module.path.as_str(), module)) + .collect::>(); + + for package in &workspace.packages { + let local_root = Path::new(&package.root).join(&package.source_root); + let package_modules = package + .modules + .iter() + .filter(|module| Path::new(&module.path).starts_with(&local_root)) + .filter_map(|module| by_path.get(module.path.as_str()).copied()) + .collect::>(); + render_package_public_api( + out, + &format!("{} {}", package.name, package.version), + &package_modules, + ); + } +} + +fn render_package_public_api(out: &mut String, package_name: &str, modules: &[&DocModule]) { + out.push_str("## Package API "); + out.push_str(package_name); + out.push_str("\n\n"); + + let public_modules = modules + .iter() + .copied() + .filter(|module| !module.public_api.is_empty()) + .collect::>(); + if public_modules.is_empty() { + out.push_str("None.\n\n"); + return; + } + + for module in public_modules { + out.push_str("### Module "); + out.push_str(&module.title); + out.push_str("\n\n"); + render_public_api_body(out, "####", &module.public_api); + } +} + +fn render_public_api_section(out: &mut String, heading: &str, public_api: &PublicApi) { + out.push_str(heading); + out.push_str(" Public API\n\n"); + render_public_api_body(out, "####", public_api); +} + +fn render_public_api_body(out: &mut String, heading: &str, public_api: &PublicApi) { + if public_api.is_empty() { + out.push_str("None.\n\n"); + return; + } + + if !public_api.functions.is_empty() { + out.push_str(heading); + out.push_str(" Functions\n"); + for function in &public_api.functions { + render_code_bullet(out, &function.signature); + } + out.push('\n'); + } + + if !public_api.structs.is_empty() { + out.push_str(heading); + out.push_str(" Structs\n"); + for struct_decl in &public_api.structs { + render_code_bullet(out, &format!("struct {}", struct_decl.name)); + for field in &struct_decl.fields { + render_indented_code_bullet(out, field); + } + } + out.push('\n'); + } + + if !public_api.enums.is_empty() { + out.push_str(heading); + out.push_str(" Enums\n"); + for enum_decl in &public_api.enums { + render_code_bullet(out, &format!("enum {}", enum_decl.name)); + for variant in &enum_decl.variants { + render_indented_code_bullet(out, variant); + } + } + out.push('\n'); + } +} + fn render_list(out: &mut String, title: &str, values: &[String]) { out.push_str("### "); out.push_str(title); @@ -242,6 +395,182 @@ fn render_list(out: &mut String, title: &str, values: &[String]) { out.push('\n'); } +fn list_head(expr: &SExpr) -> Option<&str> { + list(expr).and_then(|items| items.first()).and_then(ident) +} + +fn render_code_bullet(out: &mut String, value: &str) { + out.push_str("- `"); + out.push_str(&value.replace('`', "\\`")); + out.push_str("`\n"); +} + +fn render_indented_code_bullet(out: &mut String, value: &str) { + out.push_str(" - `"); + out.push_str(&value.replace('`', "\\`")); + out.push_str("`\n"); +} + +impl PublicApi { + fn is_empty(&self) -> bool { + self.functions.is_empty() && self.structs.is_empty() && self.enums.is_empty() + } +} + +fn public_api_from_program(program: &Program, exports: &[String]) -> PublicApi { + let export_names = exports.iter().cloned().collect::>(); + let aliases = alias_targets(program); + + let mut functions = program + .functions + .iter() + .filter(|function| export_names.contains(&function.name)) + .map(|function| DocFunction { + name: function.name.clone(), + signature: function_signature( + &function.name, + function + .params + .iter() + .map(|param| (param.name.as_str(), ¶m.ty)), + &function.return_type, + &aliases, + ), + }) + .collect::>(); + functions.sort_by(|left, right| left.name.cmp(&right.name)); + + let mut structs = program + .structs + .iter() + .filter(|struct_decl| export_names.contains(&struct_decl.name)) + .map(|struct_decl| DocStruct { + name: struct_decl.name.clone(), + fields: struct_decl + .fields + .iter() + .map(|field| { + format!( + "{}: {}", + field.name, + display_public_type(&field.ty, &aliases) + ) + }) + .collect(), + }) + .collect::>(); + structs.sort_by(|left, right| left.name.cmp(&right.name)); + + let mut enums = program + .enums + .iter() + .filter(|enum_decl| export_names.contains(&enum_decl.name)) + .map(|enum_decl| DocEnum { + name: enum_decl.name.clone(), + variants: enum_decl + .variants + .iter() + .map(|variant| match &variant.payload_ty { + Some(payload_ty) => { + format!( + "{}({})", + variant.name, + display_public_type(payload_ty, &aliases) + ) + } + None => variant.name.clone(), + }) + .collect(), + }) + .collect::>(); + enums.sort_by(|left, right| left.name.cmp(&right.name)); + + PublicApi { + functions, + structs, + enums, + } +} + +fn function_signature<'a>( + name: &str, + params: impl Iterator, + return_type: &Type, + aliases: &BTreeMap, +) -> String { + let params = params + .map(|(name, ty)| format!("{}: {}", name, display_public_type(ty, aliases))) + .collect::>() + .join(", "); + format!( + "fn {}({}) -> {}", + name, + params, + display_public_type(return_type, aliases) + ) +} + +fn alias_targets(program: &Program) -> BTreeMap { + let raw = program + .type_aliases + .iter() + .map(|alias| (alias.name.clone(), alias.target.clone())) + .collect::>(); + raw.keys() + .map(|name| { + let mut visiting = BTreeSet::new(); + ( + name.clone(), + resolve_alias_type(&Type::Named(name.clone()), &raw, &mut visiting), + ) + }) + .collect() +} + +fn display_public_type(ty: &Type, aliases: &BTreeMap) -> String { + let mut visiting = BTreeSet::new(); + resolve_alias_type(ty, aliases, &mut visiting).to_string() +} + +fn resolve_alias_type( + ty: &Type, + aliases: &BTreeMap, + visiting: &mut BTreeSet, +) -> Type { + match ty { + Type::Named(name) => { + let Some(target) = aliases.get(name) else { + return ty.clone(); + }; + if !visiting.insert(name.clone()) { + return ty.clone(); + } + let resolved = resolve_alias_type(target, aliases, visiting); + visiting.remove(name); + resolved + } + Type::Ptr(inner) => Type::Ptr(Box::new(resolve_alias_type(inner, aliases, visiting))), + Type::Array(inner, len) => { + Type::Array(Box::new(resolve_alias_type(inner, aliases, visiting)), *len) + } + Type::Vec(inner) => Type::Vec(Box::new(resolve_alias_type(inner, aliases, visiting))), + Type::Slice(inner) => Type::Slice(Box::new(resolve_alias_type(inner, aliases, visiting))), + Type::Option(inner) => Type::Option(Box::new(resolve_alias_type(inner, aliases, visiting))), + Type::Result(ok, err) => Type::Result( + Box::new(resolve_alias_type(ok, aliases, visiting)), + Box::new(resolve_alias_type(err, aliases, visiting)), + ), + Type::I32 + | Type::I64 + | Type::U32 + | Type::U64 + | Type::F64 + | Type::Bool + | Type::Unit + | Type::String => ty.clone(), + } +} + fn list(expr: &SExpr) -> Option<&[SExpr]> { match &expr.kind { SExprKind::List(items) => Some(items), diff --git a/compiler/tests/doc_api_beta11.rs b/compiler/tests/doc_api_beta11.rs new file mode 100644 index 0000000..63de2a6 --- /dev/null +++ b/compiler/tests/doc_api_beta11.rs @@ -0,0 +1,420 @@ +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::{Command, Output}, + sync::atomic::{AtomicUsize, Ordering}, +}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[test] +fn doc_file_renders_public_api_with_signatures_and_shapes() { + let source = r#"(module api (export Point Status make)) + +(struct Point + (x i32) + (label string)) + +(enum Status Ready (Blocked i32)) + +(fn helper ((value i32)) -> i32 + value) + +(fn make ((x i32) (label string)) -> Point + (Point (x x) (label label))) + +(test "make is documented" + true) +"#; + let file = write_file("file-api", source); + let docs = unique_path("file-api-docs"); + + let output = run_glagol([ + OsStr::new("doc"), + file.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + + assert_success("doc file", &output); + let index = read_index(&docs); + assert!(index.contains("## Module api")); + assert!(index.contains("### Imports\n\nNone.\n\n")); + assert!(index.contains("- `Point`")); + assert!(index.contains("- `make(x i32, label string) -> Point`")); + assert!(index.contains("- `make is documented`")); + + let api = public_api_for_module(&index, "api"); + assert!(api.contains("- `fn make(x: i32, label: string) -> Point`")); + assert!(api.contains("- `struct Point`")); + assert!(api.contains(" - `x: i32`")); + assert!(api.contains(" - `label: string`")); + assert!(api.contains("- `enum Status`")); + assert!(api.contains(" - `Ready`")); + assert!(api.contains(" - `Blocked(i32)`")); + assert!( + !api.contains("helper"), + "non-exported helper leaked into public API:\n{}", + api + ); +} + +#[test] +fn doc_project_renders_package_and_module_public_api() { + let project = write_project( + "project-api", + &[( + "math", + r#"(module math (export add Pair)) + +(struct Pair + (left i32) + (right i32)) + +(fn add ((left i32) (right i32)) -> i32 + (+ left right)) + +(fn private_double ((value i32)) -> i32 + (+ value value)) +"#, + )], + "(module main)\n\n(import math (add Pair))\n\n(fn main () -> i32\n (add 1 2))\n", + ); + let docs = unique_path("project-api-docs"); + + let output = run_glagol([ + OsStr::new("doc"), + project.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + + assert_success("doc project", &output); + let index = read_index(&docs); + assert!(index.contains("# Project project-api")); + assert!(index.contains("## Package API project-api")); + assert!(index.contains("## Module math")); + assert!(index.contains("## Module main")); + assert!(index.contains("- `math`")); + assert!(index.contains("- `add`")); + + let package_api = package_api(&index, "project-api"); + assert!(package_api.contains("### Module math")); + assert!(package_api.contains("- `fn add(left: i32, right: i32) -> i32`")); + assert!(package_api.contains("- `struct Pair`")); + assert!( + !package_api.contains("private_double"), + "non-exported function leaked into package API:\n{}", + package_api + ); + + let math_api = public_api_for_module(&index, "math"); + assert!(math_api.contains("- `fn add(left: i32, right: i32) -> i32`")); +} + +#[test] +fn doc_workspace_renders_each_package_api_deterministically() { + let workspace = unique_path("workspace-api"); + let scaffold = run_glagol([ + OsStr::new("new"), + workspace.as_os_str(), + OsStr::new("--template"), + OsStr::new("workspace"), + ]); + assert_success("workspace scaffold", &scaffold); + + let docs = unique_path("workspace-api-docs"); + let output = run_glagol([ + OsStr::new("doc"), + workspace.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + + assert_success("doc workspace", &output); + let index = read_index(&docs); + assert!(index.contains("## Workspace")); + assert!(index.contains("- `packages/app`")); + assert!(index.contains("- `packages/libutil`")); + assert!(index.contains("## Package API app 0.1.0")); + assert!(index.contains("## Package API libutil 0.1.0")); + + let app_api = package_api(&index, "app 0.1.0"); + assert!(app_api.contains("None.")); + + let lib_api = package_api(&index, "libutil 0.1.0"); + assert!(lib_api.contains("### Module libutil")); + assert!(lib_api.contains("- `fn answer() -> i32`")); + assert!(lib_api.contains("- `fn label() -> string`")); +} + +#[test] +fn doc_workspace_package_api_excludes_loaded_std_modules() { + let workspace = write_workspace_with_std_import("workspace-std-api"); + let docs = unique_path("workspace-std-api-docs"); + + let output = run_glagol([ + OsStr::new("doc"), + workspace.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + + assert_success("doc workspace std import", &output); + let index = read_index(&docs); + assert!( + index.contains("## Module option"), + "module summaries should still include loaded std module docs:\n{}", + index + ); + + let app_api = package_api(&index, "app 0.1.0"); + assert!(app_api.contains("### Module main")); + assert!(app_api.contains("- `fn local_some(value: i32) -> (option i32)`")); + assert!( + !app_api.contains("Module std.option") && !app_api.contains("Module option"), + "loaded std module leaked into package API:\n{}", + app_api + ); + assert!( + !app_api.contains("some_i32"), + "loaded std helper leaked into package API:\n{}", + app_api + ); +} + +#[test] +fn public_api_normalizes_local_aliases_and_omits_alias_exports() { + let source = r#"(module aliases (export Count Score Status measure)) + +(type Count i32) +(type MaybeCount (option Count)) + +(struct Score + (value Count) + (maybe MaybeCount)) + +(enum Status Ready (Blocked Count) (Maybe MaybeCount)) + +(fn hidden ((value Count)) -> Count + value) + +(fn measure ((value Count) (maybe MaybeCount)) -> Count + value) +"#; + let file = write_file("alias-api", source); + let docs = unique_path("alias-api-docs"); + + let output = run_glagol([ + OsStr::new("doc"), + file.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + + assert_success("doc aliases", &output); + let index = read_index(&docs); + assert!( + index.contains("- `Count`"), + "exports summary should retain the alias name" + ); + assert!( + index.contains("- `hidden(value Count) -> Count`"), + "function summary should retain non-public declarations" + ); + + let api = public_api_for_module(&index, "aliases"); + assert!(api.contains("- `fn measure(value: i32, maybe: (option i32)) -> i32`")); + assert!(api.contains(" - `value: i32`")); + assert!(api.contains(" - `maybe: (option i32)`")); + assert!(api.contains(" - `Blocked(i32)`")); + assert!(api.contains(" - `Maybe((option i32))`")); + assert!( + !api.contains("Count"), + "alias names leaked into public API:\n{}", + api + ); + assert!( + !api.contains("hidden"), + "non-exported function leaked into public API:\n{}", + api + ); +} + +#[test] +fn repeated_doc_generation_is_byte_identical() { + let source = r#"(module stable (export value)) + +(fn value () -> i32 + 42) +"#; + let file = write_file("stable-api", source); + let docs = unique_path("stable-api-docs"); + + let first = run_glagol([ + OsStr::new("doc"), + file.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + assert_success("first doc", &first); + let first_bytes = fs::read(docs.join("index.md")).expect("read first docs"); + + let second = run_glagol([ + OsStr::new("doc"), + file.as_os_str(), + OsStr::new("-o"), + docs.as_os_str(), + ]); + assert_success("second doc", &second); + let second_bytes = fs::read(docs.join("index.md")).expect("read second docs"); + + assert_eq!(first_bytes, second_bytes); +} + +fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf { + let project = unique_path(name); + fs::create_dir_all(project.join("src")).expect("create project src"); + fs::write( + project.join("slovo.toml"), + format!( + "[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n", + name + ), + ) + .expect("write manifest"); + for (module, source) in modules { + fs::write(project.join("src").join(format!("{}.slo", module)), source) + .expect("write module"); + } + fs::write(project.join("src/main.slo"), main).expect("write main"); + project +} + +fn write_workspace_with_std_import(name: &str) -> PathBuf { + let workspace = unique_path(name); + let package = workspace.join("packages/app"); + fs::create_dir_all(package.join("src")).expect("create workspace package src"); + fs::write( + workspace.join("slovo.toml"), + "[workspace]\nmembers = [\"packages/app\"]\ndefault_package = \"app\"\n", + ) + .expect("write workspace manifest"); + fs::write( + package.join("slovo.toml"), + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nsource_root = \"src\"\nentry = \"main\"\n", + ) + .expect("write package manifest"); + fs::write( + package.join("src/main.slo"), + r#"(module main (export local_some)) + +(import std.option (some_i32)) + +(fn local_some ((value i32)) -> (option i32) + (some_i32 value)) +"#, + ) + .expect("write package main"); + workspace +} + +fn write_file(name: &str, source: &str) -> PathBuf { + let path = unique_path(name).with_extension("slo"); + fs::write(&path, source).expect("write fixture"); + path +} + +fn read_index(docs: &Path) -> String { + fs::read_to_string(docs.join("index.md")).expect("read generated docs") +} + +fn package_api<'a>(docs: &'a str, package: &str) -> &'a str { + let heading = format!("## Package API {}", package); + let start = docs + .find(&heading) + .unwrap_or_else(|| panic!("missing package API heading `{}`\n{}", heading, docs)); + let rest = &docs[start..]; + let end = rest + .find("\n## Package API ") + .or_else(|| rest.find("\n## Module ")) + .unwrap_or(rest.len()); + &rest[..end] +} + +fn public_api_for_module<'a>(docs: &'a str, module: &str) -> &'a str { + let heading = format!("## Module {}", module); + let module_start = if docs.starts_with(&heading) { + 0 + } else { + let marker = format!("\n{}", heading); + docs.find(&marker) + .map(|index| index + 1) + .unwrap_or_else(|| panic!("missing module heading `{}`\n{}", heading, docs)) + }; + let module_docs = &docs[module_start..]; + let module_end = module_docs + .find("\n## Module ") + .unwrap_or(module_docs.len()); + let module_docs = &module_docs[..module_end]; + let public_start = module_docs.find("### Public API").unwrap_or_else(|| { + panic!( + "missing public API for module `{}`\n{}", + module, module_docs + ) + }); + let public_docs = &module_docs[public_start..]; + let public_end = public_docs + .find("\n### Structs") + .unwrap_or(public_docs.len()); + &public_docs[..public_end] +} + +fn unique_path(name: &str) -> PathBuf { + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system clock before UNIX_EPOCH") + .as_nanos(); + std::env::temp_dir().join(format!( + "glagol-doc-api-beta11-{}-{}-{}-{}", + std::process::id(), + nanos, + id, + name + )) +} + +fn run_glagol(args: I) -> Output +where + I: IntoIterator, + S: AsRef, +{ + Command::new(env!("CARGO_BIN_EXE_glagol")) + .args(args) + .output() + .expect("run glagol") +} + +fn assert_success(context: &str, output: &Output) { + assert!( + output.status.success(), + "{} failed\nstdout:\n{}\nstderr:\n{}", + context, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!( + output.stdout.is_empty(), + "{} wrote stdout:\n{}", + context, + String::from_utf8_lossy(&output.stdout) + ); + assert!( + output.stderr.is_empty(), + "{} wrote stderr:\n{}", + context, + String::from_utf8_lossy(&output.stderr) + ); +} diff --git a/docs/POST_BETA_ROADMAP.md b/docs/POST_BETA_ROADMAP.md index cd31f4a..c936bf2 100644 --- a/docs/POST_BETA_ROADMAP.md +++ b/docs/POST_BETA_ROADMAP.md @@ -270,7 +270,6 @@ Work: - language-server diagnostics and document symbols - editor-facing symbol metadata for files, projects, and workspaces - project watch mode -- generated API documentation for local packages - clearer benchmark harness output - machine-readable diagnostics stability policy @@ -281,12 +280,45 @@ public types. `glagol symbols ` now emits deterministic `slovo.symbols` metadata for editor integrations without starting an LSP server. This is beta API discovery only; it does not add executable generics, maps, sets, runtime changes, or a stable standard-library -API freeze. LSP, watch mode, local-package API docs, benchmark-output work, -and a machine-readable diagnostics stability policy remain deferred. +API freeze. LSP, watch mode, benchmark-output work, stable Markdown schema, +stable stdlib/API compatibility freeze, SARIF/daemon protocols, and a +machine-readable diagnostics stability policy remain deferred. Why tenth: editor support matters, but it should follow a stable enough syntax, project model, and diagnostic model. +### 11. Local Package API Documentation + +Goal: extend beta API discovery from `lib/std` and symbol metadata to the +local packages and modules users document with `glagol doc`. + +Work: + +- make `glagol doc -o ` include deterministic + exported/public API sections for local packages and modules +- list exact exported function signatures +- list exported struct fields +- list exported enum variants and payload types +- keep non-exported functions, structs, enums, tests, and aliases out of the + public API sections +- normalize module-local concrete aliases in public docs so local alias names + do not leak across module/package boundaries +- keep Markdown layout and generated file names beta-scoped rather than stable + +Released in `1.0.0-beta.11`: local file, project, package, and workspace docs +generated by `glagol doc -o ` include +deterministic public API sections with exact exported function signatures, +exported struct fields, exported enum variants/payload types, non-export +filtering, and module-local alias normalization. This extends beta10 API +discovery only; it does not freeze the Markdown schema, create a stable +stdlib/API compatibility freeze, add LSP/watch, define SARIF/daemon protocols, +set a diagnostics schema policy, implement executable generics/maps/sets, add +re-exports/globs/hierarchical modules, or define registry semantics. + +Why eleventh: local packages are useful only if their public surface can be +reviewed without reading every source file, but the documentation format +should remain flexible until the package and editor stories are stronger. + ## Stable `1.0.0` Gate Slovo should not become stable until all of these are true: diff --git a/docs/compiler/RELEASE_NOTES.md b/docs/compiler/RELEASE_NOTES.md index a80bac7..caa43fd 100644 --- a/docs/compiler/RELEASE_NOTES.md +++ b/docs/compiler/RELEASE_NOTES.md @@ -10,11 +10,42 @@ integration/readiness release, not the first real beta. ## Unreleased -Next scoped Glagol work is expected to continue after the `1.0.0-beta.10` -developer-experience API discovery and symbol-metadata update. +Next scoped Glagol work is expected to continue after the `1.0.0-beta.11` +local package API documentation update. No unreleased compiler scope is committed here yet. +## 1.0.0-beta.11 + +Release label: `1.0.0-beta.11` + +Release date: 2026-05-22 + +Release state: local package API documentation update + +### Summary + +The beta.11 docs/tooling contract extends beta.10 API discovery so generated +local documentation exposes the public API of local files, projects, packages, +and workspaces without changing source-language execution semantics. + +- `glagol doc -o ` includes deterministic + exported/public API sections for local modules and workspace packages. +- Public API sections render exact exported function signatures, exported + struct fields, and exported enum variants with payload types. +- Module-local concrete aliases are normalized in public docs before rendering. +- Non-exported functions, structs, enums, tests, and `(type ...)` aliases stay + out of the public API sections. + +### Explicit Deferrals + +This release does not define a stable Markdown schema, stable stdlib/API +compatibility freeze, an LSP server, watch mode, SARIF, daemon protocols, +diagnostics schema policy, executable generics, generic vectors, maps, sets, +iterators, re-exports, globs, hierarchical modules, package registry +semantics, runtime collection changes, new standard-library runtime APIs, or +stable ABI/layout promises. + ## 1.0.0-beta.10 Release label: `1.0.0-beta.10` diff --git a/docs/compiler/ROADMAP.md b/docs/compiler/ROADMAP.md index acb510b..effa563 100644 --- a/docs/compiler/ROADMAP.md +++ b/docs/compiler/ROADMAP.md @@ -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.10`, released on 2026-05-22 as a -developer-experience API discovery and symbol-metadata update. It keeps the `1.0.0-beta` +Current stage: `1.0.0-beta.11`, released on 2026-05-22 as a local package API +documentation 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, @@ -42,11 +42,19 @@ helper signatures after alias normalization, and adds `glagol symbols ` for deterministic editor-facing source metadata over modules, imports, exports, aliases, structs, enums, functions, tests, spans/ranges, and workspace package labels. -It adds no source-language runtime behavior. +The beta.11 documentation slice extends +`glagol doc -o ` with deterministic +exported/public API sections for local packages and modules, including exact +exported function signatures, exported struct fields, exported enum +variants/payload types, non-export filtering, and module-local alias +normalization. It adds no source-language runtime behavior. -Next stage target: post-`1.0.0-beta.10` developer-experience and -collection/generic planning. Generic vectors, maps, sets, generic stdlib -dispatch, runtime collection changes, and collection unification remain +Next stage target: post-`1.0.0-beta.11` developer-experience, +diagnostics-schema, package, and collection/generic planning. Generic vectors, +maps, sets, generic stdlib dispatch, runtime collection changes, collection +unification, stable Markdown schema, LSP/watch protocols, SARIF/daemon +protocols, re-exports/globs/hierarchical modules, diagnostics schema policy, +registry semantics, and a stable stdlib/API compatibility freeze remain unimplemented until a later scoped contract promotes them explicitly. The final experimental precursor scope is `exp-125`. Its unsigned direct-value diff --git a/docs/language/RELEASE_NOTES.md b/docs/language/RELEASE_NOTES.md index 2a3b39b..2b5aaab 100644 --- a/docs/language/RELEASE_NOTES.md +++ b/docs/language/RELEASE_NOTES.md @@ -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.10`, published on 2026-05-22. It keeps the +The current release is `1.0.0-beta.11`, 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 @@ -19,13 +19,45 @@ loopback networking foundation bundle from `1.0.0-beta.6`, and the first serialization/data-interchange foundation bundle from `1.0.0-beta.7`, and the first concrete type alias foundation from `1.0.0-beta.8`, plus the first collection alias unification and generic reservation slice from -`1.0.0-beta.9`, and the first developer-experience API discovery slice from -`1.0.0-beta.10`. +`1.0.0-beta.9`, the first developer-experience API discovery slice from +`1.0.0-beta.10`, and the local package API documentation extension from +`1.0.0-beta.11`. ## Unreleased No unreleased language scope is committed here yet. +## 1.0.0-beta.11 + +Release label: `1.0.0-beta.11` + +Release name: Local Package API Documentation + +Release date: 2026-05-22 + +Status: released beta documentation/API-discovery update on the +`1.0.0-beta` language baseline. + +`1.0.0-beta.11` extends the beta.10 API discovery lane from the generated +standard-library catalog and `glagol symbols` metadata into local package and +module documentation: + +- `glagol doc -o ` includes deterministic + exported/public API sections for local source files, projects, packages, and + workspaces. +- Public API sections list exact exported function signatures, exported struct + fields, and exported enum variants with payload types. +- Module-local concrete aliases are normalized before public rendering, so + local names do not leak into package/module API docs. +- Non-exported functions, structs, enums, tests, and `(type ...)` aliases + remain omitted from the public API surface. + +This release does not define a stable Markdown schema, stable stdlib/API +compatibility freeze, LSP server, watch mode, SARIF, daemon protocols, +diagnostics schema policy, executable generics, maps, sets, re-exports, globs, +hierarchical modules, package registry semantics, runtime changes, new +compiler-known runtime names, or stable ABI/layout promises. + ## 1.0.0-beta.10 Release label: `1.0.0-beta.10` diff --git a/docs/language/ROADMAP.md b/docs/language/ROADMAP.md index d6410b6..9008646 100644 --- a/docs/language/ROADMAP.md +++ b/docs/language/ROADMAP.md @@ -10,8 +10,8 @@ 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.10`, released on 2026-05-22 as the first post-beta -developer-experience API discovery update. It keeps the `1.0.0-beta` language contract and +Current stage: `1.0.0-beta.11`, released on 2026-05-22 as a post-beta local +package API documentation 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 @@ -24,18 +24,24 @@ inside the current concrete vector, option, and result facades, plus a generated standard-library API catalog that lists exact exported helper signatures with module-local aliases normalized to concrete public types, plus `glagol symbols` deterministic source metadata for files, projects, and -workspaces. JSON -parsing, recursive JSON values, executable generics, generic aliases, +workspaces, plus `glagol doc -o ` public API +sections for local packages/modules with exact exported function signatures, +exported struct fields, exported enum variants/payload types, non-export +filtering, and module-local alias normalization. JSON parsing, recursive JSON +values, executable generics, generic aliases, parameterized aliases, cross-module alias visibility, maps/sets, traits, inference, monomorphization, iterators, runtime changes for generic collections, 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. +rich host-error ADTs, stable ABI/layout, stable Markdown schema, stable +stdlib/API compatibility freeze, LSP/watch, SARIF/daemon protocols, +diagnostics schema policy, re-exports/globs/hierarchical modules, and package +registry semantics remain deferred. -Next stage target: continue from developer-experience and reserved -generic/map/set diagnostics without claiming executable generics, an LSP/watch -protocol, or stable standard-library APIs until the exact scope is frozen from -the manifest and roadmap. +Next stage target: continue from developer-experience, diagnostics, package, +and reserved generic/map/set planning without claiming executable generics, an +LSP/watch protocol, stable Markdown schema, registry semantics, or +stable standard-library/API compatibility freeze until the exact scope is +frozen from the manifest and roadmap. The final experimental precursor scope is `exp-125`, defined in `.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its diff --git a/docs/language/SPEC-v1.md b/docs/language/SPEC-v1.md index de9a0d0..c563fba 100644 --- a/docs/language/SPEC-v1.md +++ b/docs/language/SPEC-v1.md @@ -2,8 +2,8 @@ Status: living beta contract for `1.0.0-beta`, with the post-beta `1.0.0-beta.1` tooling/install update, `1.0.0-beta.2` runtime/resource -foundation update, and `1.0.0-beta.10` developer-experience API discovery -update. The language contract integrates +foundation update, `1.0.0-beta.10` developer-experience API discovery update, +and `1.0.0-beta.11` local package API documentation update. The language contract integrates promoted language slices through `exp-125` and the historical publication baseline through `exp-123`. `1.0.0-beta` is the first real general-purpose beta release. `exp-125` completed the unsigned numeric and stdlib breadth @@ -129,6 +129,16 @@ Current v1 release surface and explicit experimental targets: for files, projects, and workspaces; this is beta API discovery only, not executable generics, maps, sets, runtime changes, LSP/watch, or a stable `1.0.0` standard-library freeze +- `1.0.0-beta.11` local package API documentation target: + `glagol doc -o ` includes deterministic + exported/public API sections for local packages and modules with exact + exported function signatures, exported struct fields, exported enum + variants/payload types, non-export filtering, and module-local alias + normalization; this remains beta API discovery only, not a stable Markdown + schema, stable stdlib/API + compatibility freeze, LSP/watch contract, SARIF/daemon protocol, diagnostics + schema policy, executable generics, maps/sets, re-exports, globs, + hierarchical modules, or registry semantics - `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 @@ -953,6 +963,14 @@ structs, functions, and tests. It is a documentation generator, not a semantic reflection API, and it does not expose stable typed-core, debug metadata, source-map, ABI, layout, runtime reflection, or compiler-internal contracts. +As of `1.0.0-beta.11`, `glagol doc -o ` also +includes deterministic exported/public API sections for local packages and +modules. These sections render exact exported function signatures, exported +struct fields, and exported enum variants with payload types. They omit +non-exported functions, structs, enums, tests, and aliases from the public API +surface and normalize module-local concrete aliases before rendering public +types. + `glagol symbols ` emits deterministic machine-readable S-expression metadata using the `slovo.symbols` schema. It reports module paths, package labels when available, imports, exports, @@ -1075,6 +1093,25 @@ solving, package publishing, archive formats, optional/dev/target dependencies, feature flags, package build scripts, or stable package ABI/layout promises. +`1.0.0-beta.11` extends the generated local-package documentation surface, but +not the package model itself. `glagol doc -o ` +includes deterministic exported/public API sections for local modules and +workspace packages: + +- exported functions are rendered with exact parameter and return types +- exported structs are rendered with exported field names and field types +- exported enums are rendered with exported variant names and payload types +- non-exported functions, structs, enums, tests, and `(type ...)` aliases are + excluded from the public API sections +- module-local concrete aliases are normalized to their concrete public target + types before rendering + +The generated Markdown remains a beta documentation format. This does not +define a stable Markdown schema, stable stdlib/API compatibility freeze, +LSP/watch behavior, SARIF or daemon protocols, diagnostics schema policy, +executable generics, maps/sets, re-exports, glob imports, hierarchical modules, +registry semantics, runtime behavior, or stable ABI/layout. + ### 4.4.4 Post-Beta Networking Foundation Status: released in `1.0.0-beta.6`. @@ -1232,6 +1269,44 @@ generic aliases, parameterized aliases, maps, sets, traits, inference, monomorphization, iterators, new runtime helpers, stable ABI/layout promises, or a stable standard-library API freeze. +### 4.4.8 Post-Beta Local Package API Documentation + +Status: released in `1.0.0-beta.11` as a documentation/API-discovery update on +top of the beta.10 discovery lane. + +`1.0.0-beta.11` extends `glagol doc -o ` so the +generated Markdown includes deterministic exported/public API sections for +local source files, projects, packages, and workspaces. The public API surface +is derived from explicit module exports and local package boundaries. + +The public API sections include: + +- exact exported function signatures, including parameter names, parameter + types, and return types +- exported struct field names and field types +- exported enum variant names and payload types, including payloadless variants + and current single-payload variants +- deterministic ordering that follows the existing module/package + documentation ordering + +Non-exported functions, structs, enums, tests, and `(type ...)` aliases are +not part of the public API sections. A declaration can still appear in other +source-structure summaries when those summaries already exist, but it must not +be presented as exported/public API unless the module export list exposes it. + +Module-local concrete aliases are normalized before public rendering. Public +docs show concrete target types such as `(vec i32)`, `(option string)`, and +`(result u64 i32)` rather than private alias names introduced by the +documented module. This mirrors beta.10 `lib/std` catalog behavior for local +packages and modules. + +This is not a stable documentation schema. It does not freeze Markdown +headings, anchors, file names, machine parsing contracts, stable stdlib/API +compatibility freeze, LSP/watch behavior, SARIF or daemon protocols, +diagnostics schema policy, executable generics, maps/sets, re-exports, glob +imports, hierarchical modules, registry semantics, runtime behavior, package +ABI, or layout. + ## 4.5 v2.0.0-beta.1 Experimental Integration Readiness Status: current experimental Slovo-side release contract, released 2026-05-17. @@ -5678,12 +5753,18 @@ manifest source root and uses deterministic v1.3 module ordering. Plain `glagol fmt ` continues to write formatted source to stdout. Documentation generation records modules, imports/exports, structs, functions, -and tests as deterministic Markdown. It is generated documentation, not -runtime reflection, typed-core reflection, debug metadata, DWARF, source maps, -or ABI/layout information. +tests, and beta.11 exported/public API sections as deterministic Markdown. +Public API sections include exact exported function signatures, exported +struct fields, exported enum variants/payload types, non-export filtering, and +module-local alias normalization. It is generated documentation, not runtime +reflection, typed-core reflection, debug metadata, DWARF, source maps, +ABI/layout information, or a stable Markdown schema. -LSP, watch mode, daemon protocols, SARIF, debug adapters, stable debug -metadata, DWARF emission, and standalone source-map files remain deferred. +LSP, watch mode, daemon protocols, SARIF, debug adapters, diagnostics schema +policy, stable debug metadata, DWARF emission, standalone source-map files, +stable stdlib/API compatibility freeze, executable generics, maps/sets, +re-exports, globs, hierarchical modules, and registry semantics remain +deferred. ## 10. Slice 6: Unsafe, Memory, And FFI diff --git a/docs/language/STDLIB_API.md b/docs/language/STDLIB_API.md index ed07fd4..307c655 100644 --- a/docs/language/STDLIB_API.md +++ b/docs/language/STDLIB_API.md @@ -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.10`; future releases may mark new helpers this way before they graduate. +- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.11`; 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 API discovery aid, not a stable `1.0.0` standard-library freeze.