diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__ambiguous_bare_index_label.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__ambiguous_bare_index_label.json index b7d5203a2..5c1aec76f 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__ambiguous_bare_index_label.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__ambiguous_bare_index_label.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 1, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__complex_extern_unsupported.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__complex_extern_unsupported.json index 7014ebb2c..ec2866ed9 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__complex_extern_unsupported.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__complex_extern_unsupported.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 15, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_concrete_index.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_concrete_index.json index fa9741ed4..6c733e179 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_concrete_index.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_concrete_index.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 8, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_unbound_result_index.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_unbound_result_index.json index 3611d5208..8c1cdb110 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_unbound_result_index.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_array_unbound_result_index.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 40, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_conflicting_signature.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_conflicting_signature.json index 61033019e..beea3dce9 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_conflicting_signature.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_conflicting_signature.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 79, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_duplicate_param.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_duplicate_param.json index 534db9ac1..ad83072ea 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_duplicate_param.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_duplicate_param.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P011", + "code": "graphcal::W011", "labels": [ { "length": 1, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_in_const.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_in_const.json index 8a204e214..cd2c4d46e 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_in_const.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_in_const.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P004", + "code": "graphcal::W004", "labels": [ { "length": 10, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_missing_host_fn.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_missing_host_fn.json index 7cb737b6e..1f5ca0bc4 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_missing_host_fn.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_missing_host_fn.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P003", + "code": "graphcal::W003", "labels": [ { "length": 12, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_generic_return.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_generic_return.json index 787baed70..7f8c91d2f 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_generic_return.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_generic_return.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 4, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_union_return.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_union_return.json index 139eefcda..6e58167fd 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_union_return.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_struct_union_return.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 7, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_unknown_fn.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_unknown_fn.json index ffd60ac77..802e726a2 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_unknown_fn.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_unknown_fn.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P002", + "code": "graphcal::W002", "labels": [ { "length": 13, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_use_before_binding.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_use_before_binding.json index 8fc9b97fa..80d178eb3 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_use_before_binding.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__extern_use_before_binding.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::P001", + "code": "graphcal::W001", "labels": [ { "length": 27, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_cross_file_unknown_dag__src__lib__main.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_cross_file_unknown_dag__src__lib__main.json index bf747b1c4..04b3d0ad9 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_cross_file_unknown_dag__src__lib__main.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_cross_file_unknown_dag__src__lib__main.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 16, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_errors__unknown_dag.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_errors__unknown_dag.json index 7eb398bef..586220cef 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_errors__unknown_dag.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__inline_dag_call_errors__unknown_dag.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 10, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__auto_assert_module__src__lib__main.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__auto_assert_module__src__lib__main.json index 0e15389e9..27f08d948 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__auto_assert_module__src__lib__main.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__auto_assert_module__src__lib__main.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 13, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__bad_name_import__src__bad_name_import__main.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__bad_name_import__src__bad_name_import__main.json index ffde3de1d..ee6c33424 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__bad_name_import__src__bad_name_import__main.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__bad_name_import__src__bad_name_import__main.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 185, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_graph_ref__src__params__main.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_graph_ref__src__params__main.json index 3ece5af09..35f69ac47 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_graph_ref__src__params__main.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_graph_ref__src__params__main.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 14, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_mixed__src__lib__main.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_mixed__src__lib__main.json index ca5db41ff..4436c210c 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_mixed__src__lib__main.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__multi__module_import_mixed__src__lib__main.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 12, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__prelude_dimension_as_value.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__prelude_dimension_as_value.json index ab00fd030..ce4b8d806 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__prelude_dimension_as_value.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__prelude_dimension_as_value.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 6, diff --git a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__unknown_const_ref.json b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__unknown_const_ref.json index dbaba51a2..3ac0d548c 100644 --- a/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__unknown_const_ref.json +++ b/crates/graphcal-cli/tests/output_golden/tests__fixtures__invalid__unknown_const_ref.json @@ -1,6 +1,6 @@ { "check": { - "code": "graphcal::M033", + "code": "graphcal::M036", "labels": [ { "length": 7, diff --git a/crates/graphcal-compiler/src/semantic_error/module.rs b/crates/graphcal-compiler/src/semantic_error/module.rs index 869eda6ae..4a3387453 100644 --- a/crates/graphcal-compiler/src/semantic_error/module.rs +++ b/crates/graphcal-compiler/src/semantic_error/module.rs @@ -144,7 +144,7 @@ impl DiagnosticKind for ModuleError { Self::IndexBindingNotAnIndex { .. } => "graphcal::M019", Self::IndexKindMismatch { .. } => "graphcal::M018", Self::ImportRuntimeItem { .. } => "graphcal::M020", - Self::ModuleResolution { .. } => "graphcal::M033", + Self::ModuleResolution { .. } => "graphcal::M036", Self::AmbiguousModulePath { .. } => "graphcal::M034", } } diff --git a/crates/graphcal-compiler/src/semantic_error/plugin.rs b/crates/graphcal-compiler/src/semantic_error/plugin.rs index 09b32d087..6abd5f32e 100644 --- a/crates/graphcal-compiler/src/semantic_error/plugin.rs +++ b/crates/graphcal-compiler/src/semantic_error/plugin.rs @@ -180,16 +180,16 @@ pub enum PluginLoadFailure { impl DiagnosticKind for PluginError { fn code(&self) -> &'static str { match self { - Self::UnknownExternFunction { .. } => "graphcal::P002", - Self::InvalidExternSignature { .. } => "graphcal::P001", - Self::DuplicateExternParameter { .. } => "graphcal::P011", - Self::MissingHostFunction { .. } => "graphcal::P003", - Self::ExternCallNotAllowed { .. } => "graphcal::P004", - Self::ExternSignatureMismatch { .. } => "graphcal::P005", - Self::PluginLoadFailed { .. } => "graphcal::P006", - Self::PluginForbiddenImport { .. } => "graphcal::P007", - Self::PluginNotPinned { .. } => "graphcal::P009", - Self::PluginHashMismatch { .. } => "graphcal::P010", + Self::UnknownExternFunction { .. } => "graphcal::W002", + Self::InvalidExternSignature { .. } => "graphcal::W001", + Self::DuplicateExternParameter { .. } => "graphcal::W011", + Self::MissingHostFunction { .. } => "graphcal::W003", + Self::ExternCallNotAllowed { .. } => "graphcal::W004", + Self::ExternSignatureMismatch { .. } => "graphcal::W005", + Self::PluginLoadFailed { .. } => "graphcal::W006", + Self::PluginForbiddenImport { .. } => "graphcal::W007", + Self::PluginNotPinned { .. } => "graphcal::W009", + Self::PluginHashMismatch { .. } => "graphcal::W010", } } diff --git a/crates/graphcal-compiler/src/semantic_error/tests.rs b/crates/graphcal-compiler/src/semantic_error/tests.rs index 1576b0f74..4c60270cb 100644 --- a/crates/graphcal-compiler/src/semantic_error/tests.rs +++ b/crates/graphcal-compiler/src/semantic_error/tests.rs @@ -58,7 +58,9 @@ fn every_family_contributes_codes_with_its_own_prefix() { } #[test] -fn diagnostic_codes_are_unique_and_reassignments_are_pinned() { +fn diagnostic_code_reassignments_are_pinned() { + // Workspace-wide uniqueness is checked by + // `crates/graphcal-project/tests/diagnostic_codes.rs`. let mut catalog = family_code_catalog(); let internal = crate::internal_error::InternalError::CODE; catalog.insert( @@ -67,13 +69,6 @@ fn diagnostic_codes_are_unique_and_reassignments_are_pinned() { ); assert!(catalog.len() > 100, "incomplete catalog: {catalog:?}"); - let mut variants_by_code = BTreeMap::new(); - for (variant, code) in &catalog { - if let Some(previous) = variants_by_code.insert(code, variant) { - panic!("diagnostic code `{code}` is shared by `{previous}` and `{variant}`"); - } - } - for (variant, expected) in [ ("LinearAlgebraShapeMismatch", "D022"), ("AggregationCardinalityUnknown", "D027"), @@ -83,6 +78,9 @@ fn diagnostic_codes_are_unique_and_reassignments_are_pinned() { ("InvalidEpochTimeScaleArgument", "D029"), ("UnsupportedEpochTimeScale", "D030"), ("ImportRuntimeItem", "M020"), + ("ModuleResolution", "M036"), + ("InvalidExternSignature", "W001"), + ("PluginHashMismatch", "W010"), ("InternalError", "X001"), ] { assert_eq!(catalog.get(variant).map(String::as_str), Some(expected)); @@ -217,7 +215,7 @@ fn module_resolution_keeps_ambiguous_paths_apart_and_cycles_name_their_templates let unknown = ModuleError::resolution(ModuleResolveError::UnknownModule { owner: second.clone(), }); - assert_eq!(unknown.code(), "graphcal::M033"); + assert_eq!(unknown.code(), "graphcal::M036"); assert_eq!( unknown.to_string(), ModuleResolveError::UnknownModule { diff --git a/crates/graphcal-lsp/src/diagnostics.rs b/crates/graphcal-lsp/src/diagnostics.rs index 720c0caf4..baeb859cb 100644 --- a/crates/graphcal-lsp/src/diagnostics.rs +++ b/crates/graphcal-lsp/src/diagnostics.rs @@ -1069,7 +1069,7 @@ param event: Datetime( assert_eq!(diagnostics.len(), 1, "{diagnostics:?}"); assert!(matches!( &diagnostics[0].code, - Some(NumberOrString::String(code)) if code == "graphcal::P011" + Some(NumberOrString::String(code)) if code == "graphcal::W011" )); assert!(diagnostics[0].related_information.is_some()); } diff --git a/crates/graphcal-plugin-host/tests/project_eval.rs b/crates/graphcal-plugin-host/tests/project_eval.rs index 586f8e053..ec5728f75 100644 --- a/crates/graphcal-plugin-host/tests/project_eval.rs +++ b/crates/graphcal-plugin-host/tests/project_eval.rs @@ -1046,7 +1046,7 @@ node bad: Bool[Item] = scalars::invert(@numbers); #[test] fn declared_array_signature_must_match_the_manifest() { // The declaration says the result reuses the input's index variable but - // over a *different* element dimension — structural mismatch, P005. + // over a *different* element dimension — structural mismatch, W005. let dir = tempfile::tempdir().unwrap(); let source = r#" import plugin "plugins/arrays.wasm" as arrays { diff --git a/crates/graphcal-plugin/tests/prelude_drift.rs b/crates/graphcal-plugin/tests/prelude_drift.rs index f240f2250..424d09f5a 100644 --- a/crates/graphcal-plugin/tests/prelude_drift.rs +++ b/crates/graphcal-plugin/tests/prelude_drift.rs @@ -5,7 +5,7 @@ //! verified against the real thing: for every vocabulary name, a `.gcl` //! extern declaration spelling that name is compiled against the //! macro-produced manifest signature. The loader's structural verification -//! (P005) then proves both sides denote the same dimension — if the +//! (W005) then proves both sides denote the same dimension — if the //! macro's table ever disagrees with the prelude, these tests fail. #![cfg(test)] @@ -153,7 +153,7 @@ fn every_vocabulary_name_matches_the_prelude() { let dir = tempfile::tempdir().expect("tempdir"); let registry = registry_from_embedded_manifest(); // Structural verification of every declaration against the macro's - // manifest happens during compilation; any table drift is a P005. + // manifest happens during compilation; any table drift is a W005. compile(dir.path(), GCL_DECLARATIONS, ®istry) .expect("macro vocabulary must match the prelude"); } @@ -161,7 +161,7 @@ fn every_vocabulary_name_matches_the_prelude() { #[test] fn drift_would_be_detected() { // Negative control: declare one function with the wrong signature and - // require P005, proving the positive test actually verifies signatures. + // require W005, proving the positive test actually verifies signatures. let dir = tempfile::tempdir().expect("tempdir"); let registry = registry_from_embedded_manifest(); let source = r#" diff --git a/crates/graphcal-project/src/project_compiler/pipeline.rs b/crates/graphcal-project/src/project_compiler/pipeline.rs index 580cfcef7..6e6966768 100644 --- a/crates/graphcal-project/src/project_compiler/pipeline.rs +++ b/crates/graphcal-project/src/project_compiler/pipeline.rs @@ -329,9 +329,9 @@ pub(super) fn check_hir_project( /// the plugin file must have been read by the loader, and the plugin /// host must have registered the module (its recorded failure is /// reported otherwise). -/// 2. The registry must provide the function (`MissingHostFunction`, P003). +/// 2. The registry must provide the function (`MissingHostFunction`, W003). /// 3. When the registry entry carries a manifest-provided signature, the -/// declared signature must be structurally equivalent to it (P005) — +/// declared signature must be structurally equivalent to it (W005) — /// this is the "declaration verified against the embedded manifest" /// guarantee of the plugin design (#25). fn verify_host_functions( diff --git a/crates/graphcal-project/tests/diagnostic_codes.rs b/crates/graphcal-project/tests/diagnostic_codes.rs new file mode 100644 index 000000000..51c7c7e42 --- /dev/null +++ b/crates/graphcal-project/tests/diagnostic_codes.rs @@ -0,0 +1,186 @@ +//! Workspace-wide guard for the stable `graphcal::` diagnostic code namespace. +//! +//! Diagnostic codes are assigned in several crates and in two syntaxes: +//! `DiagnosticKind::code()` match arms (`Self::X => "graphcal::P001"`) and +//! miette attributes (`code(graphcal::M033)`). This test discovers every such +//! assignment in non-test sources itself, so a new error enum is covered +//! without registering it anywhere. +#![allow( + clippy::expect_used, + reason = "an unreadable workspace source tree should fail the guard immediately" +)] + +use std::collections::BTreeMap; +use std::fmt; +use std::path::{Path, PathBuf}; + +/// One place in the sources that assigns a stable code. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct CodeSite { + file: PathBuf, + line: usize, +} + +impl fmt::Display for CodeSite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.file.display(), self.line) + } +} + +fn workspace_crates_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("crate lives under `crates/`") + .to_path_buf() +} + +/// Every non-test `.rs` file under each crate's `src/`. +fn non_test_sources(crates_dir: &Path) -> Vec { + fn walk(dir: &Path, out: &mut Vec) { + for entry in std::fs::read_dir(dir).expect("readable source dir") { + let path = entry.expect("readable dir entry").path(); + let name = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); + let is_test_module = + name == "tests" || name == "tests.rs" || name.ends_with("_tests.rs"); + let is_rust = path.extension().is_some_and(|ext| ext == "rs"); + match (path.is_dir(), is_rust && !is_test_module) { + (true, _) if !is_test_module => walk(&path, out), + (false, true) => out.push(path), + _ => {} + } + } + } + let mut sources = Vec::new(); + for entry in std::fs::read_dir(crates_dir).expect("readable crates dir") { + let src = entry.expect("readable dir entry").path().join("src"); + if src.is_dir() { + walk(&src, &mut sources); + } + } + sources.sort(); + sources +} + +/// The code (`P001`) assigned at byte `start`, if the text there is +/// `graphcal::<3 digits>` closed by `"` or `)`. +fn code_at(text: &str, start: usize) -> Option<&str> { + let code = text.get(start..start + 4)?; + let mut chars = code.chars(); + let well_formed = chars.next().is_some_and(|c| c.is_ascii_uppercase()) + && chars.all(|c| c.is_ascii_digit()) + && matches!(text.as_bytes().get(start + 4), Some(b'"' | b')')); + well_formed.then_some(code) +} + +/// Code assignments in `text`, ignoring everything from the first inline +/// `#[cfg(test)]` module on (which also skips frozen legacy copies kept +/// for regression tests). +fn code_assignments(text: &str) -> Vec<(usize, &str)> { + const MARKERS: [&str; 3] = [ + "=> \"graphcal::", // `DiagnosticKind::code()` arm + "code(graphcal::", // miette attribute + ": &'static str = \"graphcal::", // associated `CODE` constant + ]; + let text = text + .find("#[cfg(test)]\nmod ") + .map_or(text, |end| &text[..end]); + let mut found: Vec<(usize, &str)> = MARKERS + .iter() + .flat_map(|marker| { + text.match_indices(marker).filter_map(|(at, _)| { + let line = text[..at].matches('\n').count() + 1; + code_at(text, at + marker.len()).map(|code| (line, code)) + }) + }) + .collect(); + found.sort_unstable(); + found +} + +fn workspace_code_sites() -> BTreeMap> { + let crates_dir = workspace_crates_dir(); + let mut sites: BTreeMap> = BTreeMap::new(); + for path in non_test_sources(&crates_dir) { + let text = std::fs::read_to_string(&path).expect("readable source file"); + let file = path + .strip_prefix(&crates_dir) + .expect("source under crates dir") + .to_path_buf(); + for (line, code) in code_assignments(&text) { + sites.entry(code.to_owned()).or_default().push(CodeSite { + file: file.clone(), + line, + }); + } + } + sites +} + +#[test] +fn every_diagnostic_code_is_assigned_at_exactly_one_site() { + let sites = workspace_code_sites(); + assert!(sites.len() > 200, "incomplete catalog: {sites:?}"); + for family in ["graphcal-compiler", "graphcal-project"] { + assert!( + sites + .values() + .flatten() + .any(|site| site.file.starts_with(family)), + "no codes discovered in `{family}`" + ); + } + + let shared: Vec = sites + .iter() + .filter(|(_, sites)| sites.len() > 1) + .map(|(code, sites)| { + let sites: Vec = sites.iter().map(ToString::to_string).collect(); + format!("{code}: {}", sites.join(", ")) + }) + .collect(); + assert!( + shared.is_empty(), + "diagnostic codes assigned at more than one site:\n{}", + shared.join("\n") + ); +} + +#[test] +fn each_source_file_assigns_codes_from_a_single_prefix() { + let mut prefixes_by_file: BTreeMap> = BTreeMap::new(); + for (code, sites) in workspace_code_sites() { + let prefix = code.chars().next().expect("non-empty code"); + for site in sites { + prefixes_by_file + .entry(site.file.clone()) + .or_default() + .entry(prefix) + .or_insert(site); + } + } + for (file, prefixes) in prefixes_by_file { + assert!( + prefixes.len() == 1, + "`{}` mixes code prefixes: {prefixes:?}", + file.display() + ); + } +} + +#[test] +fn code_assignments_recognize_every_syntax_and_skip_test_modules() { + let source = "\ + Self::A { .. } | Self::B => \"graphcal::P001\", + #[diagnostic(code(graphcal::M033), help(\"see graphcal::M034\"))] + #[diagnostic(code(graphcal::cli::O001))] + pub const CODE: &'static str = \"graphcal::X001\"; +#[cfg(test)] +mod tests { + #[diagnostic(code(graphcal::P002))] +} +"; + assert_eq!( + code_assignments(source), + vec![(1, "P001"), (2, "M033"), (4, "X001")] + ); +} diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_ambiguous_bare_index_label.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_ambiguous_bare_index_label.snap index 457e9754e..3fe07afb1 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_ambiguous_bare_index_label.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_ambiguous_bare_index_label.snap @@ -9,4 +9,4 @@ Begin snippet for ambiguous_bare_index_label.gcl starting at line 4, column 1 snippet line 4: snippet line 5: node x: Dimensionless = A; label at line 5, column 25: error here -diagnostic code: graphcal::M033 +diagnostic code: graphcal::M036 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_complex_extern_unsupported.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_complex_extern_unsupported.snap index 9b721a477..71bfcab7a 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_complex_extern_unsupported.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_complex_extern_unsupported.snap @@ -11,4 +11,4 @@ snippet line 2: fn identity(z: Complex) -> Complex; label at line 2, columns 20 to 34: invalid signature snippet line 3: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_concrete_index.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_concrete_index.snap index ab444fc11..36072fe37 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_concrete_index.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_concrete_index.snap @@ -11,4 +11,4 @@ snippet line 6: fn boost(xs: Velocity[Maneuver]) -> Velocity; label at line 6, columns 27 to 34: invalid signature snippet line 7: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_unbound_result_index.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_unbound_result_index.snap index c9cfc352f..4f175bf31 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_unbound_result_index.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_array_unbound_result_index.snap @@ -11,4 +11,4 @@ snippet line 4: fn make(x: D) -> D[I]; label at line 4, columns 5 to 44: invalid signature snippet line 5: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_conflicting_signature.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_conflicting_signature.snap index b6cda12b3..f1b93d53d 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_conflicting_signature.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_conflicting_signature.snap @@ -11,4 +11,4 @@ snippet line 6: fn lerp(a: Dimensionless, b: Dimensionless, t: Dimensionless label at line 6, columns 5 to 83: invalid signature snippet line 7: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_duplicate_param.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_duplicate_param.snap index 77cd929e5..353560475 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_duplicate_param.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_duplicate_param.snap @@ -12,4 +12,4 @@ snippet line 2: fn lerp(a: D, a: D, t: Dimensionless) -> D; label at line 2, column 27: duplicate parameter snippet line 3: } diagnostic help: each extern function parameter name must be unique -diagnostic code: graphcal::P011 +diagnostic code: graphcal::W011 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_in_const.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_in_const.snap index 8baba41db..b07db1bb1 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_in_const.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_in_const.snap @@ -10,4 +10,4 @@ snippet line 4: } snippet line 5: const node c: Dimensionless = demo::lerp(1.0, 2.0, 0.5); label at line 5, columns 31 to 40: extern call not allowed here diagnostic help: extern functions are runtime-provided and can only be called from runtime expressions (nodes, param defaults, and asserts) -diagnostic code: graphcal::P004 +diagnostic code: graphcal::W004 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_missing_host_fn.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_missing_host_fn.snap index 9aa1695ba..b0d4386cc 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_missing_host_fn.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_missing_host_fn.snap @@ -12,4 +12,4 @@ snippet line 4: fn not_provided(x: Dimensionless) -> Dimensionless; label at line 4, columns 8 to 19: missing host function snippet line 5: } diagnostic help: the embedder's host function registry has no entry for this declared extern function -diagnostic code: graphcal::P003 +diagnostic code: graphcal::W003 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_generic_return.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_generic_return.snap index 46143210b..4b308cd09 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_generic_return.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_generic_return.snap @@ -11,4 +11,4 @@ snippet line 6: fn bounds(x: Length) -> Pair; label at line 6, columns 29 to 32: invalid signature snippet line 7: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_union_return.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_union_return.snap index ce4027bc9..d04041c73 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_union_return.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_struct_union_return.snap @@ -11,4 +11,4 @@ snippet line 9: fn solve(x: Dimensionless) -> Outcome; label at line 9, columns 35 to 41: invalid signature snippet line 10: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_unknown_fn.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_unknown_fn.snap index ff1f1253d..b65d9fee0 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_unknown_fn.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_unknown_fn.snap @@ -10,4 +10,4 @@ snippet line 4: } snippet line 5: node x: Dimensionless = demo::missing(1.0); label at line 5, columns 25 to 37: unknown extern function diagnostic help: extern functions must be declared in the plugin's `import plugin ... { ... }` block -diagnostic code: graphcal::P002 +diagnostic code: graphcal::W002 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_use_before_binding.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_use_before_binding.snap index 80c5127ed..c30795c4a 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_use_before_binding.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_extern_use_before_binding.snap @@ -11,4 +11,4 @@ snippet line 3: fn sq(x: D^2) -> D; label at line 3, columns 5 to 31: invalid signature snippet line 4: } diagnostic help: extern signatures support Bool, Int, quantity types, indexed collections of those scalar kinds over one or more declared index variables, and record struct returns with concrete fields; each dimension variable must be declared in the `<...>` binder list and bound by a bare quantity parameter or bare quantity-array element before compound uses, and every result axis must reuse an index variable that indexes some parameter -diagnostic code: graphcal::P001 +diagnostic code: graphcal::W001 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_prelude_dimension_as_value.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_prelude_dimension_as_value.snap index a762b750b..4aaff274d 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_prelude_dimension_as_value.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_prelude_dimension_as_value.snap @@ -9,4 +9,4 @@ Begin snippet for prelude_dimension_as_value.gcl starting at line 1, column 1 snippet line 1: // Error: prelude dimensions are type-system names, not values. snippet line 2: node x: Dimensionless = Length; label at line 2, columns 25 to 30: error here -diagnostic code: graphcal::M033 +diagnostic code: graphcal::M036 diff --git a/crates/graphcal-project/tests/snapshots/error_snapshots__error_unknown_const_ref.snap b/crates/graphcal-project/tests/snapshots/error_snapshots__error_unknown_const_ref.snap index 151112d70..4509fc065 100644 --- a/crates/graphcal-project/tests/snapshots/error_snapshots__error_unknown_const_ref.snap +++ b/crates/graphcal-project/tests/snapshots/error_snapshots__error_unknown_const_ref.snap @@ -9,4 +9,4 @@ Begin snippet for unknown_const_ref.gcl starting at line 1, column 1 snippet line 1: // Unknown constant reference: UNKNOWN is never declared snippet line 2: node x: Dimensionless = UNKNOWN + 1.0; label at line 2, columns 25 to 31: error here -diagnostic code: graphcal::M033 +diagnostic code: graphcal::M036 diff --git a/docs/en/authoring-plugins.md b/docs/en/authoring-plugins.md index 5ff4f5a60..048e932aa 100644 --- a/docs/en/authoring-plugins.md +++ b/docs/en/authoring-plugins.md @@ -121,7 +121,7 @@ expanded to base-dimension exponents in the manifest, so `Pressure` and `Mass * Length^-1 * Time^-2` declare the same contract. Two rules mirror the compiler's checks (violations are compile errors in -the plugin crate, with the same meaning as P005/P016 on the graphcal +the plugin crate, with the same meaning as W001/P016 on the graphcal side): - every dimension variable must first appear as a **bare** parameter type diff --git a/docs/en/language/extern-functions.md b/docs/en/language/extern-functions.md index 21a4a5a54..da862b226 100644 --- a/docs/en/language/extern-functions.md +++ b/docs/en/language/extern-functions.md @@ -75,7 +75,7 @@ against, reviewable in plain-text diffs and usable by editor tooling without the binary. At load time, each declaration is **verified structurally against the manifest embedded in the `.wasm` module** — renaming dimension variables or parameters is fine, but any difference in -dimensional shape is a compile error (P005), so drift between source and +dimensional shape is a compile error (W005), so drift between source and binary can never be silently reinterpreted. If a dimensional signature names an unknown qualified path, the diagnostic preserves that complete path instead of flattening it into a dimension leaf. @@ -177,7 +177,7 @@ The plugin's manifest never learns the type's *name* — it declares the flattened field shape (names, order, and kinds), and the declaration binds that shape to the nominal record type. Field names and order are part of the contract: a plugin declaring `{min, max}` does not match a -declaration whose record has `{lo, hi}` (P005). The result evaluates to +declaration whose record has `{lo, hi}` (W005). The result evaluates to an ordinary record-shaped algebraic value with working field access and matching. The plugin ABI calls its flattened representation a `struct` shape; this is not a separate Graphcal type category. @@ -207,7 +207,7 @@ node v_mid: Velocity = demo::lerp(@v0, @v1, 0.25); Restrictions, all enforced at compile time: - Extern functions are **runtime-provided**, so they cannot appear in - `const` expressions, domain bounds, or unit scale expressions (P004). + `const` expressions, domain bounds, or unit scale expressions (W004). - Calls must be alias-qualified; a bare `lerp(...)` is an unknown function. - There is **no auto-lifting** over indexed values: an extern quantity @@ -274,7 +274,7 @@ at load time before any plugin code runs: memory, allocator state, and `start` side effects cannot carry history from one graph node or re-evaluation into another. Together these rules make the boundary pure by construction. A module importing WASI or other host APIs is - rejected with a dedicated diagnostic (P007). A module importing `graphcal::fail` + rejected with a dedicated diagnostic (W007). A module importing `graphcal::fail` must export its linear memory as `"memory"` so the failure message can be read. - **Resource bounds.** Plugin modules may be at most 16 MiB by default. @@ -379,8 +379,8 @@ sha256 = "3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855c" ``` At load time the pin is enforced, hard errors and never prompts: a plugin -without a pin fails with P009 ("run `graphcal deps lock`"), and a plugin -whose bytes hash differently from the pin fails with P010. New or changed +without a pin fails with W009 ("run `graphcal deps lock`"), and a plugin +whose bytes hash differently from the pin fails with W010. New or changed plugin code can therefore only enter the project through a reviewable `graphcal.lock` diff. Plugin artifacts must be regular files within the package root, not symbolic links, and are subject to project loading limits. @@ -422,7 +422,7 @@ graphcal's per-node containment model: If a declared extern function is missing entirely — the plugin file is absent, fails validation, or its manifest does not provide the function — that is a **load-time** error reported on the declaration before -evaluation starts (P003, P005–P010 depending on the cause). +evaluation starts (W003, W005–W010 depending on the cause). ## The Host Function Registry diff --git a/docs/ja/authoring-plugins.md b/docs/ja/authoring-plugins.md index 0b040cfa7..0b768a667 100644 --- a/docs/ja/authoring-plugins.md +++ b/docs/ja/authoring-plugins.md @@ -121,7 +121,7 @@ graphcal_plugin::plugin! { `Mass * Length^-1 * Time^-2` は同じ契約を宣言します。 2 つのルールがコンパイラーの検査を反映しています (違反はプラグインクレートの -コンパイルエラーとなり、graphcal 側の P005/P016 と同じ意味を +コンパイルエラーとなり、graphcal 側の W001/P016 と同じ意味を 持ちます): - すべての次元変数は、複合的な使用 diff --git a/docs/ja/language/extern-functions.md b/docs/ja/language/extern-functions.md index 2e3fd15e0..2482e2794 100644 --- a/docs/ja/language/extern-functions.md +++ b/docs/ja/language/extern-functions.md @@ -33,7 +33,7 @@ import plugin "plugins/fluids.wasm" as fluids { - **エイリアス**(`as fluids`)は必須です。extern 関数は `fluids::density(...)` のようにエイリアスで修飾してのみ呼び出せ、裸の名前では呼び出せません。これにより、モジュールのインポートと同じ明示性が保たれ、組み込み関数の名前空間は閉じたままになります。 - 各 `fn` は**名前付きパラメーター**と**結果の型**を宣言します。パラメーターと結果の型には、`Bool`、`Int`、次元式で記述する量の型、または 1 つ以上の宣言済みインデックス変数を軸とする、それらのスカラー種類の配列(`flags: Bool[I]`、`counts: Int[I]`、`xs: D[I]`、`matrix: D[I, J]`)を指定できます。結果にはさらに、スコープ内のレコード型も指定できます([レコード形の結果](#record-shaped-results)を参照)。 -シグネチャはプラグインから推論せず、明示的に宣言します。ソース内の宣言がプロジェクトの型検査で使う契約となり、プレーンテキストの差分でレビューでき、バイナリがなくてもエディターツールで利用できます。読み込み時には、各宣言が **`.wasm` モジュールに埋め込まれたマニフェストに対して構造的に検証**されます。次元変数やパラメーターの改名は許されますが、次元の構造に相違があればコンパイルエラー(P005)になるため、ソースとバイナリのずれが黙って別の意味に解釈されることはありません。次元シグネチャに未知の修飾パスがある場合、診断はその完全なパスを保持し、次元の末端名へと平坦化しません。 +シグネチャはプラグインから推論せず、明示的に宣言します。ソース内の宣言がプロジェクトの型検査で使う契約となり、プレーンテキストの差分でレビューでき、バイナリがなくてもエディターツールで利用できます。読み込み時には、各宣言が **`.wasm` モジュールに埋め込まれたマニフェストに対して構造的に検証**されます。次元変数やパラメーターの改名は許されますが、次元の構造に相違があればコンパイルエラー(W005)になるため、ソースとバイナリのずれが黙って別の意味に解釈されることはありません。次元シグネチャに未知の修飾パスがある場合、診断はその完全なパスを保持し、次元の末端名へと平坦化しません。 ## 次元変数 { #dimension-variables } @@ -98,7 +98,7 @@ node dv_span: DvSpan = stats::span(@dv); node spread: Velocity = @dv_span.lo - @dv_span.hi; ``` -プラグインのマニフェストは型の*名前*を知りません。平坦化されたフィールド構造(名前、順序、種類)を宣言し、ソースの宣言がその構造を公称レコード型に束縛します。フィールド名と順序は契約の一部です。`{min, max}` を宣言するプラグインは、レコードが `{lo, hi}` である宣言とは一致しません(P005)。結果は通常のレコード形の代数的な値として評価され、フィールドアクセスとパターンマッチが使えます。プラグイン ABI はその平坦表現を `struct` 形と呼びますが、これは Graphcal における別の型カテゴリではありません。 +プラグインのマニフェストは型の*名前*を知りません。平坦化されたフィールド構造(名前、順序、種類)を宣言し、ソースの宣言がその構造を公称レコード型に束縛します。フィールド名と順序は契約の一部です。`{min, max}` を宣言するプラグインは、レコードが `{lo, hi}` である宣言とは一致しません(W005)。結果は通常のレコード形の代数的な値として評価され、フィールドアクセスとパターンマッチが使えます。プラグイン ABI はその平坦表現を `struct` 形と呼びますが、これは Graphcal における別の型カテゴリではありません。 この段階では以下の制約があり、それぞれ専用のコンパイルエラーがあります。 @@ -119,7 +119,7 @@ node v_mid: Velocity = demo::lerp(@v0, @v1, 0.25); 以下の制約はすべてコンパイル時に検査されます。 -- extern 関数は**実行時に提供される**ため、`const` 式、定義域の境界、単位のスケール式には使えません(P004)。 +- extern 関数は**実行時に提供される**ため、`const` 式、定義域の境界、単位のスケール式には使えません(W004)。 - 呼び出しはエイリアスで修飾しなければなりません。裸の `lerp(...)` は未知の関数になります。 - インデックス付きの値に対する**自動リフティングはありません**。extern の量関数を要素ごとに適用するには、明示的な `for` 内包表記を使い、反復をソース上に見える形にします。 @@ -138,7 +138,7 @@ graphcal プラグインは**コア WebAssembly モジュール**です。プロ - **マニフェスト。** モジュールは `graphcal-manifest` というカスタムセクションに JSON マニフェストを埋め込み、`abi_version: 5` と各提供関数の次元シグネチャ(次元変数、インデックス変数、名前付きパラメーター、配列の種類と struct のフィールドレイアウトを含む結果)を宣言します。固定次元は、プレリュードの 8 つの基本次元(`Length`、`Time`、`Mass`、`Temperature`、`ElectricCurrent`、`Amount`、`LuminousIntensity`、`Angle`)を有理数指数で組み合わせた構造として記述します。たとえば `Velocity` は `Length^1 * Time^-1` です。量の種類には JSON タグ `"quantity"` を使います。配列のエントリーには、量、`"bool"`、`"int"` のいずれかの明示的な要素種類があります。ユーザー定義の基本次元はバイナリ境界を越えられません。JSON ペイロードの上限は 256 KiB、関数数の上限は 256 です。名前は UTF-8 で最大 256 バイトです。各関数が宣言できる次元変数、インデックス変数、パラメーターはそれぞれ最大 32 個です。配列の軸は最大 31、struct の結果の平坦化されたフィールドは最大 256、各単項式の変数因子と固定次元因子の合計は最大 64 です。 - **値の ABI。** 各関数の wasm エクスポート型はシグネチャに従います。量/`Bool`/`Int` パラメーターはそれぞれ 1 つの `f64` ABI スロットを使います(量は生の SI 基本単位、`Int` は正確に表現可能な整数、`Bool` は `1.0`/`0.0`)。ランク `R` の配列パラメーターは `i32` ポインターと、それに続く `R` 個の `i32` 軸長で表現され、形状の積に等しい個数の密なリトルエンディアン・行優先の `f64` 要素を指します。量の要素は有限でなければなりません。Bool の要素は数値の `0.0` または `1.0`(`-0.0` は false)で、Int の要素にはスカラー Int と同じ厳密な binary64 ポリシーが適用されます。すべての要素は、型付きのランタイム/プラグインコードに入る前と、結果を受け取る際の両方で検証されます。量/`Bool`/`Int` の結果は単一の `f64` 戻り値です。配列または struct の結果では戻り値の代わりに末尾の `i32` 出力ポインターを使い、プラグインがそこへ書き込みます。配列ではシグネチャで束縛された結果の軸長の積、struct ではフィールドごとに 1 スロットです。変換後の完全な関数シグネチャで使える生の WebAssembly パラメーターは最大 32 個で、配列ポインター、各軸の長さ、出力ポインターはそれぞれ別々に数えます。これはマニフェストと Rust SDK によりコード生成前に検査されます。非有限の量には graphcal の通常の非有限値の封じ込めが適用されます。 - **アロケーターのエクスポート。** 配列または struct を受け取るか返すモジュールは、メモリを `"memory"` として、さらに `graphcal_alloc(size: i32) -> i32`(8 バイト境界に整列)と `graphcal_free(ptr: i32, size: i32)` をエクスポートしなければなりません。ホストは呼び出し前にすべてのバッファを確保し、入力を書き込み、結果を読み取った後ですべて解放します。プラグインが呼び出しをまたいでバッファを保持することはありません。 -- **インポートの禁止。** モジュールは何もインポートできません。ただし、ホストが提供する失敗報告用の `graphcal::fail(ptr: i32, len: i32)` だけは例外です。インポートの禁止によりプラグインは I/O を行えません。また、ホストは論理的な呼び出しごとに新しいインスタンスを作るため、可変グローバル、テーブル、線形メモリ、アロケーター状態、`start` の副作用が、あるグラフノードや再評価から別のものへ履歴を持ち越せません。これらの規則により、境界の純粋性が構造的に保たれます。WASI やその他のホスト API をインポートするモジュールは、専用の診断(P007)で拒否されます。`graphcal::fail` をインポートするモジュールは、失敗メッセージを読めるよう、線形メモリを `"memory"` としてエクスポートしなければなりません。 +- **インポートの禁止。** モジュールは何もインポートできません。ただし、ホストが提供する失敗報告用の `graphcal::fail(ptr: i32, len: i32)` だけは例外です。インポートの禁止によりプラグインは I/O を行えません。また、ホストは論理的な呼び出しごとに新しいインスタンスを作るため、可変グローバル、テーブル、線形メモリ、アロケーター状態、`start` の副作用が、あるグラフノードや再評価から別のものへ履歴を持ち越せません。これらの規則により、境界の純粋性が構造的に保たれます。WASI やその他のホスト API をインポートするモジュールは、専用の診断(W007)で拒否されます。`graphcal::fail` をインポートするモジュールは、失敗メッセージを読めるよう、線形メモリを `"memory"` としてエクスポートしなければなりません。 - **リソースの上限。** プラグインモジュールのサイズは、デフォルトで最大 16 MiB です。呼び出しには fuel 予算(おおむね命令数)とメモリの上限があり、超えるとエラーになります。fuel 予算の設定は[プロジェクトの fuel ポリシー](#project-fuel-policies)を参照してください。 - **決定性。** プラグインの算術は IEEE-754 に従って決定論的であり、数学処理もモジュール内にコンパイルされるため、結果はプラットフォーム間でビット単位で一致します。 @@ -200,7 +200,7 @@ path = "plugins/fluids.wasm" sha256 = "3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855c" ``` -読み込み時には固定値を強制し、確認プロンプトではなく必ずエラーにします。固定値のないプラグインは P009(「`graphcal deps lock` を実行してください」)で失敗し、バイト列のハッシュが固定値と異なるプラグインは P010 で失敗します。したがって、新規または変更されたプラグインコードがプロジェクトに入るには、レビュー可能な `graphcal.lock` の差分を通過する必要があります。プラグイン成果物はパッケージルート内の通常ファイルでなければならず、シンボリックリンクは使えません。プロジェクトの読み込み上限も適用されます。 +読み込み時には固定値を強制し、確認プロンプトではなく必ずエラーにします。固定値のないプラグインは W009(「`graphcal deps lock` を実行してください」)で失敗し、バイト列のハッシュが固定値と異なるプラグインは W010 で失敗します。したがって、新規または変更されたプラグインコードがプロジェクトに入るには、レビュー可能な `graphcal.lock` の差分を通過する必要があります。プラグイン成果物はパッケージルート内の通常ファイルでなければならず、シンボリックリンクは使えません。プロジェクトの読み込み上限も適用されます。 パッケージごとの扱いは次のとおりです。 @@ -227,7 +227,7 @@ extern 関数は実行時に失敗することがあります(プラグイン - そのノードに依存するノードは `dependency failed` を報告します。 - 無関係のノードは評価を続けます。呼び出しが失敗するとプラグインのインスタンスも破棄されるため、壊れたプラグインが後続の呼び出しを破損させることはありません。 -宣言された extern 関数がまったく存在しない場合(プラグインファイルがない、検証に失敗する、マニフェストにその関数がない)は、評価開始前に宣言上で報告される**読み込み時**エラーです(原因に応じて P003、P005–P010)。 +宣言された extern 関数がまったく存在しない場合(プラグインファイルがない、検証に失敗する、マニフェストにその関数がない)は、評価開始前に宣言上で報告される**読み込み時**エラーです(原因に応じて W003、W005–W010)。 ## ホスト関数レジストリ { #the-host-function-registry } diff --git a/internals/codebase-reading-guide.md b/internals/codebase-reading-guide.md index 5ae66c0fd..05b93f609 100644 --- a/internals/codebase-reading-guide.md +++ b/internals/codebase-reading-guide.md @@ -2240,23 +2240,24 @@ figure, static or live, is themed. 547. `crates/graphcal-project/tests/todo.rs` 548. `crates/graphcal-project/tests/dag_body_import_regressions.rs` 549. `crates/graphcal-project/tests/constructor_constraint_regressions.rs` -550. `crates/graphcal-report/tests/report.rs` -551. `crates/graphcal-wasm/tests/tutorial_examples.rs` -552. `crates/graphcal-wasm/tests/wasm_runtime.rs` -553. `crates/graphcal-wasm/tests/wasm_presentation.rs` -554. `crates/graphcal-wasm/tests/playground_examples.rs` -555. `crates/graphcal-wasm/tests/playground_experiments.rs` -556. `crates/graphcal-plugin-host/tests/runtime.rs` -557. `crates/graphcal-plugin-host/tests/project_eval.rs` -558. `crates/graphcal-fmt/tests/todo.rs` -559. `crates/graphcal-fmt/tests/format_tests.rs` -560. `crates/graphcal-cli/tests/todo.rs` -561. `crates/graphcal-cli/tests/cli.rs` -562. `crates/graphcal-cli/tests/plugin_cmd.rs` -563. `crates/graphcal-cli/tests/plugin_e2e.rs` -564. `crates/graphcal-cli/tests/dump.rs` -565. `crates/graphcal-cli/tests/presentation.rs` -566. `crates/graphcal-cli/tests/report_engine.rs` -567. `crates/graphcal-cli/tests/output_golden.rs` +550. `crates/graphcal-project/tests/diagnostic_codes.rs` +551. `crates/graphcal-report/tests/report.rs` +552. `crates/graphcal-wasm/tests/tutorial_examples.rs` +553. `crates/graphcal-wasm/tests/wasm_runtime.rs` +554. `crates/graphcal-wasm/tests/wasm_presentation.rs` +555. `crates/graphcal-wasm/tests/playground_examples.rs` +556. `crates/graphcal-wasm/tests/playground_experiments.rs` +557. `crates/graphcal-plugin-host/tests/runtime.rs` +558. `crates/graphcal-plugin-host/tests/project_eval.rs` +559. `crates/graphcal-fmt/tests/todo.rs` +560. `crates/graphcal-fmt/tests/format_tests.rs` +561. `crates/graphcal-cli/tests/todo.rs` +562. `crates/graphcal-cli/tests/cli.rs` +563. `crates/graphcal-cli/tests/plugin_cmd.rs` +564. `crates/graphcal-cli/tests/plugin_e2e.rs` +565. `crates/graphcal-cli/tests/dump.rs` +566. `crates/graphcal-cli/tests/presentation.rs` +567. `crates/graphcal-cli/tests/report_engine.rs` +568. `crates/graphcal-cli/tests/output_golden.rs`