diff --git a/crates/rue-compiler/src/api_inventory.rs b/crates/rue-compiler/src/api_inventory.rs index a314c9199..2eeaae8a9 100644 --- a/crates/rue-compiler/src/api_inventory.rs +++ b/crates/rue-compiler/src/api_inventory.rs @@ -43,7 +43,6 @@ const PRODUCTION_MODULES: &[(&str, &str)] = &[ ("parsed_modules", include_str!("parsed_modules.rs")), ("program_image_plan", include_str!("program_image_plan.rs")), ("queries", include_str!("queries.rs")), - ("query_graph", include_str!("query_graph.rs")), ( "revisioned_query_database", include_str!("revisioned_query_database.rs"), @@ -300,29 +299,6 @@ const RUE_866_INTERNAL_VOCABULARY: &[&str] = &[ "ResolvedCodegenRevision", "ResolvedLinkRevision", "ResolvedProgramRevision", - "SemanticDependencyBlocker", - "SemanticDependencyIncompleteReason", - "SemanticDependencyInputManifest", - "SemanticDependencyManifestWork", - "SemanticDependencySurface", - "SemanticFullInvalidationReason", - "SemanticInvalidationPlan", - "SemanticInvalidationScope", - "SemanticInvalidationWork", - "StableBodyDependencyInputRecord", - "StableBuiltinTypeCallHeadInput", - "StableDeclarationTypeCallHeadDependency", - "StableDeclarationTypeDependency", - "StableDefinitionFingerprint", - "StableDefinitionFingerprintPrecision", - "StableDefinitionInputFingerprint", - "StableFreeFunctionDependency", - "StableModuleImportDependency", - "StableNamedConstDependency", - "StableNamedConstDependencyTarget", - "StableNamedDestructorDependency", - "StableNamedMethodDependency", - "StableNamedMethodDependencyTarget", "CodegenInputDescriptor", "LinkInputDescriptor", "SemanticInputDescriptor", @@ -344,36 +320,14 @@ const RUE_866_INTERNAL_VOCABULARY: &[&str] = &[ ]; const RUE_867_DURABLE_VOCABULARY: &[&str] = &[ - "DURABLE_ORDINARY_BODY_SCHEMA_VERSION", - "DURABLE_SPECIALIZED_BODY_SCHEMA_VERSION", - "DurableAirInst", "DurableAirInstData", - "DurableAirRef", - "DurableBodyAnchor", - "DurableBodyConversionFailure", - "DurableBodyProjectionFailure", "DurableBodyWork", - "DurableCallArg", - "DurableMatchArm", - "DurableOrdinaryBody", - "DurableOrdinaryBodyPayload", - "DurablePattern", - "DurablePlace", - "DurablePlaceRef", "DurableProjection", - "DurableSpecializedBody", - "DurableSpecializedBodyPayload", - "convert_semantic_specialized_body_exports", - "DURABLE_SEMANTIC_SCHEMA_VERSION", "DurableConstValue", "DurableDeclarationPayload", "DurableDeclarationSemantic", - "DurableParameterMode", - "DurableSemanticImportEpoch", - "DurableSemanticParameter", "DurableSemanticProjectionFailure", "DurableSemanticProjectionWork", - "DurableSemanticSchemaVersion", "DurableType", ]; @@ -1132,7 +1086,6 @@ fn facade_stays_small_and_session_centered() { .map(|(name, _)| *name) .chain([ "api_inventory", - "durable_compatibility_tests", "integration_tests", "pipeline_tests", "producer_nominal_acceptance_tests", @@ -1196,7 +1149,7 @@ fn per_body_query_boundary_is_stable_independent_and_cache_free() { panic!("reviewed body query item is balanced") } let body = include_str!("body_query.rs"); - let key = item(body, "struct BodyQueryKey"); + let key = item(body, "pub(crate) struct BodyQueryKey"); assert!(key.contains("instance: crate::FunctionInstanceKey")); assert!( key.contains("configuration: crate::semantic_query_nucleus::SemanticQueryConfiguration") @@ -1214,7 +1167,7 @@ fn per_body_query_boundary_is_stable_independent_and_cache_free() { ); } - let transaction = item(body, "enum BodyTransaction {"); + let transaction = item(body, "pub(crate) enum BodyTransaction {"); assert!(transaction.contains("Success")); assert!(transaction.contains("DeterministicFailure")); assert!(!transaction.contains("Canceled")); @@ -1231,7 +1184,10 @@ fn per_body_query_boundary_is_stable_independent_and_cache_free() { "missing independent family {family}" ); } - let semantic_entry = item(include_str!("session.rs"), "struct SemanticCacheEntry"); + let semantic_entry = item( + include_str!("canonical_semantic.rs"), + "pub struct CanonicalSemanticOutput", + ); assert!(!semantic_entry.contains("successful_body_cache")); let body_transaction = source_between_exact_boundaries( runtime, @@ -1695,8 +1651,6 @@ fn durable_cache_schema_cannot_return_to_the_public_facade() { assert!(!facade.contains("pub mod durable_semantics;")); let session = include_str!("session.rs"); - let manifest_public = - public_signatures(inherent_impl(session, "SemanticDependencyInputManifest")); let semantic_public = public_signatures(inherent_impl( include_str!("canonical_semantic.rs"), "CanonicalSemanticOutput", @@ -1706,7 +1660,7 @@ fn durable_cache_schema_cannot_return_to_the_public_facade() { "durable_ordinary_bodies", ] { assert!( - !manifest_public.contains(&format!("pub fn {raw_accessor}")) + !session.contains(raw_accessor) && !semantic_public.contains(&format!("pub fn {raw_accessor}")), "raw durable schema accessor returned to a public signature: {raw_accessor}" ); @@ -1741,7 +1695,7 @@ fn query_attempts_have_one_family_owned_representation() { } #[test] -fn revisioned_parse_family_has_no_peer_legacy_authority() { +fn revisioned_parse_family_is_runtime_registered_without_a_selection_wrapper() { let session = include_str!("session.rs"); let runtime = include_str!("revisioned_query_database.rs"); for removed in [ @@ -1758,7 +1712,11 @@ fn revisioned_parse_family_has_no_peer_legacy_authority() { } assert!(session.contains(".source_revision(&source, snapshot)")); assert!(session.contains("revisioned.select_parse(&attempt)")); - assert!(runtime.contains("parse: RevisionedFamily")); + assert!(!runtime.contains("RevisionedFamily")); + assert!(!runtime.contains("selected_state_shim")); + assert!(runtime.contains("parse: QueryFamily<")); + assert!(runtime.contains("parse_selection: QuerySelection<")); + assert!(runtime.contains("content_addressed_family_with_equality(")); } #[test] @@ -2369,17 +2327,19 @@ fn canonical_semantic_body_has_no_compiler_owned_peer_algebra() { "compiler-owned canonical body mirror returned: {removed}" ); } - assert!( - durable_body - .contains("pub body: rue_air::SemanticBody"), - "durable envelope must retain rue-air's canonical body algebra directly" - ); - assert!( - durable_body.contains( - "rue_air::SemanticSpecializationIdentity" - ), - "specialized envelopes must retain rue-air's canonical identity directly" - ); + assert!(durable_body.contains("pub type DurableProjection")); + assert!(durable_body.contains("pub type DurableAirInstData")); + for removed in [ + "DurableOrdinaryBodyPayload", + "DurableSpecializedBodyPayload", + "convert_semantic_body_exports", + "convert_semantic_specialized_body_exports", + ] { + assert!( + !durable_body.contains(removed), + "removed peer body-export payload returned: {removed}" + ); + } } #[test] diff --git a/crates/rue-compiler/src/bound_definitions.rs b/crates/rue-compiler/src/bound_definitions.rs index 909628c0f..a614d274f 100644 --- a/crates/rue-compiler/src/bound_definitions.rs +++ b/crates/rue-compiler/src/bound_definitions.rs @@ -218,12 +218,10 @@ impl BoundDefinitionRecord { pub fn declaration_span(&self) -> Span { self.declaration_span } + #[cfg(test)] pub fn visibility(&self) -> Option { self.visibility } - pub(crate) fn input_partition(&self) -> BoundDefinitionInputPartition { - self.input_partition.clone() - } pub(crate) fn body_span(&self) -> Option { match self.input_partition { BoundDefinitionInputPartition::Body { body, .. } => Some(body), @@ -250,6 +248,7 @@ pub struct BoundDefinitionSet { source_revision: SourceRevision, issuer: Arc, definitions: Arc<[BoundDefinitionRecord]>, + #[allow(dead_code)] manifest_work: SemanticBindingManifestWork, work: BoundDefinitionWork, } @@ -321,6 +320,7 @@ impl BoundDefinitionSet { projected } + #[allow(dead_code)] pub(crate) fn structurally_eq(&self, other: &Self) -> bool { self.source_revision == other.source_revision && self.manifest_work == other.manifest_work @@ -345,6 +345,7 @@ impl BoundDefinitionSet { &self.definitions } #[cfg(test)] + #[allow(dead_code)] pub fn manifest_work(&self) -> SemanticBindingManifestWork { self.manifest_work } @@ -488,6 +489,7 @@ impl BoundDefinitionSet { .ok_or(rue_air::SemanticStableResolutionFailure::Missing) } + #[cfg(test)] pub(crate) fn key_for_body_token( &self, token: rue_air::BodyOwnerToken, @@ -496,6 +498,7 @@ impl BoundDefinitionSet { .map(BoundDefinitionRecord::stable_key) } + #[cfg(test)] pub(crate) fn definition_for_body_token( &self, token: rue_air::BodyOwnerToken, diff --git a/crates/rue-compiler/src/canonical_lower.rs b/crates/rue-compiler/src/canonical_lower.rs index bbe2f434b..beba13643 100644 --- a/crates/rue-compiler/src/canonical_lower.rs +++ b/crates/rue-compiler/src/canonical_lower.rs @@ -107,6 +107,7 @@ pub struct CanonicalRirPresentationOrder { } impl CanonicalRirOutput { + #[allow(dead_code)] pub(crate) fn structurally_eq(&self, other: &Self) -> bool { self.source_revision == other.source_revision && RirPrinter::new(&self.rir, self.symbols.interner()).to_string() diff --git a/crates/rue-compiler/src/canonical_semantic.rs b/crates/rue-compiler/src/canonical_semantic.rs index aa817234c..af41d821e 100644 --- a/crates/rue-compiler/src/canonical_semantic.rs +++ b/crates/rue-compiler/src/canonical_semantic.rs @@ -1,14 +1,9 @@ //! One-pass canonical declaration binding, body analysis, and CFG lowering. -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; use rue_air::{ - AnalyzedBodyOwnerEvent, BodyAnalysisWork, BodyNamedDependencyEvent, DeclarationBindingWork, - DeclarationBuiltinTypeCallHeadDependencyEvent, DeclarationTypeCallHeadDependencyEvent, - DeclarationTypeDependencyEvent, NamedConstDependencyEvent, NamedDestructorDependencyEvent, - NamedMethodDependencyEvent, OrdinaryFreeFunctionDependencyEvent, RirDeclarationIndexWork, - SemanticBindingManifestWork, SpecializedFreeFunctionDependencyEvent, - SpecializedFreeFunctionOrigin, + BodyAnalysisWork, DeclarationBindingWork, RirDeclarationIndexWork, SemanticBindingManifestWork, }; use tracing::info_span; @@ -68,6 +63,7 @@ thread_local! { } #[cfg(test)] +#[allow(dead_code)] pub(crate) fn with_test_cfg_failure_injection(run: impl FnOnce() -> T) -> T { struct Reset; impl Drop for Reset { @@ -111,6 +107,7 @@ pub(crate) fn take_test_cfg_import_failure() -> bool { } #[cfg(test)] +#[allow(dead_code)] pub(crate) fn with_test_declaration_failure_injection(run: impl FnOnce() -> T) -> T { struct Reset; impl Drop for Reset { @@ -130,6 +127,7 @@ pub(crate) fn with_test_declaration_failure_injection(run: impl FnOnce() -> T } #[cfg(test)] +#[allow(dead_code)] pub(crate) fn with_test_authoritative_key_mismatch(run: impl FnOnce() -> T) -> T { struct Reset; impl Drop for Reset { @@ -462,8 +460,6 @@ impl CanonicalSemanticWork { query.ordinary_body_exports_succeeded + query.specialized_body_exports_succeeded; durable.export_rejections += query.ordinary_body_exports_rejected + query.specialized_body_exports_rejected; - durable.conversion_attempts += query.bodies_succeeded; - durable.conversion_completions += query.bodies_succeeded; durable.instructions_exported += query.ordinary_body_export_instructions_emitted + query.specialized_body_export_instructions_emitted; durable.places_exported += query.ordinary_body_export_places_emitted @@ -574,6 +570,10 @@ fn declaration_stage_work( #[derive(Debug)] pub struct CanonicalSemanticOutput { input: CodegenInputDescriptor, + /// The exact RIR terminal whose symbol universe indexes this semantic + /// output. This is part of the canonical semantic artifact rather than a + /// session-selected side cache. + rir: Arc, functions: Vec, type_pool: FrozenTypeInternPool, strings: Vec, @@ -584,33 +584,7 @@ pub struct CanonicalSemanticOutput { /// issuer identity crosses this retention boundary. anonymous_nominal_associations: Arc<[crate::AnonymousNominalKey]>, body_owner_issuer: BoundDefinitionSet, - durable_ordinary_body_payloads: Arc<[crate::DurableOrdinaryBodyPayload]>, - durable_specialized_body_payloads: Arc<[crate::DurableSpecializedBodyPayload]>, work: CanonicalSemanticWork, - analyzed_body_owners: Vec, - body_named_dependencies: Vec, - ordinary_free_function_dependencies: Vec, - ordinary_free_function_dependencies_complete: bool, - specialized_free_function_origins: Vec, - specialized_free_function_dependencies: Vec, - specialized_free_function_dependencies_complete: bool, - named_method_dependencies: Vec, - non_generic_named_method_dependencies_complete: bool, - generic_named_method_dependencies_complete: bool, - named_destructor_dependencies: Vec, - named_destructor_dependencies_complete: bool, - declaration_type_dependencies: Vec, - declaration_type_dependencies_complete: bool, - declaration_type_call_head_dependencies: Vec, - declaration_type_call_head_dependencies_complete: bool, - declaration_builtin_type_call_head_dependencies: - Vec, - supported_type_call_heads_complete: bool, - named_const_dependencies: Vec, - named_value_const_dependencies_complete: bool, - implicit_named_destructor_dependencies: Vec, - implicit_named_destructor_dependencies_complete: bool, - body_references: BTreeMap, } impl CanonicalSemanticOutput { @@ -656,89 +630,10 @@ impl CanonicalSemanticOutput { ); record!("strings", &self.strings); record!("warnings", &self.warnings); - record!("analyzed_body_owners", &self.analyzed_body_owners); - record!("body_named_dependencies", &self.body_named_dependencies); - record!( - "ordinary_free_function_dependencies", - &self.ordinary_free_function_dependencies - ); - record!( - "specialized_free_function_origins", - &self.specialized_free_function_origins - ); - record!( - "specialized_free_function_dependencies", - &self.specialized_free_function_dependencies - ); - record!( - "ordinary_free_function_dependencies_complete", - self.ordinary_free_function_dependencies_complete - ); - record!( - "specialized_free_function_dependencies_complete", - self.specialized_free_function_dependencies_complete - ); - record!("named_method_dependencies", &self.named_method_dependencies); - record!( - "non_generic_named_method_dependencies_complete", - self.non_generic_named_method_dependencies_complete - ); - record!( - "generic_named_method_dependencies_complete", - self.generic_named_method_dependencies_complete - ); - record!( - "named_destructor_dependencies", - &self.named_destructor_dependencies - ); - record!( - "named_destructor_dependencies_complete", - self.named_destructor_dependencies_complete - ); - record!( - "declaration_type_dependencies", - &self.declaration_type_dependencies - ); - record!( - "declaration_type_dependencies_complete", - self.declaration_type_dependencies_complete - ); - record!( - "declaration_type_call_head_dependencies", - &self.declaration_type_call_head_dependencies - ); - record!( - "declaration_type_call_head_dependencies_complete", - self.declaration_type_call_head_dependencies_complete - ); - record!( - "declaration_builtin_type_call_head_dependencies", - &self.declaration_builtin_type_call_head_dependencies - ); - record!( - "supported_type_call_heads_complete", - self.supported_type_call_heads_complete - ); - record!("named_const_dependencies", &self.named_const_dependencies); - record!( - "named_value_const_dependencies_complete", - self.named_value_const_dependencies_complete - ); - record!( - "implicit_named_destructor_dependencies", - &self.implicit_named_destructor_dependencies - ); - record!( - "implicit_named_destructor_dependencies_complete", - self.implicit_named_destructor_dependencies_complete - ); - record!( - "durable_artifact_status", - self.unstable_durable_artifact_status() - ); crate::unstable::SemanticParitySnapshot::new(details) } + #[cfg_attr(not(test), allow(dead_code))] pub(crate) fn into_parts( self, ) -> ( @@ -750,10 +645,31 @@ impl CanonicalSemanticOutput { (self.functions, self.type_pool, self.strings, self.warnings) } + pub(crate) fn into_parts_with_rir( + self, + ) -> ( + Arc, + Vec, + FrozenTypeInternPool, + Vec, + Vec, + ) { + ( + self.rir, + self.functions, + self.type_pool, + self.strings, + self.warnings, + ) + } + /// Exact semantic and optimization identity of this output. pub(crate) fn input(&self) -> &CodegenInputDescriptor { &self.input } + pub(crate) fn rir_owner(&self) -> &Arc { + &self.rir + } /// Debug-only identity projection for differential and benchmark tooling. pub(crate) fn unstable_input_debug(&self) -> String { format!("{:?}", self.input) @@ -778,80 +694,9 @@ impl CanonicalSemanticOutput { pub(crate) fn accrue_body_query_work(&mut self, query: BodyAnalysisWork) { self.work.accrue_body_query_work(query); } - pub(crate) fn install_body_references( - &mut self, - references: BTreeMap, - ) { - self.body_references = references; - } - pub(crate) fn body_references( - &self, - function: &crate::FunctionInstanceKey, - ) -> Option<&crate::body_query::BodyReferences> { - self.body_references.get(function) - } - pub fn analyzed_body_owners(&self) -> &[AnalyzedBodyOwnerEvent] { - &self.analyzed_body_owners - } - pub fn body_named_dependencies(&self) -> &[BodyNamedDependencyEvent] { - &self.body_named_dependencies - } - pub fn declaration_type_dependencies(&self) -> &[DeclarationTypeDependencyEvent] { - &self.declaration_type_dependencies - } - pub fn declaration_type_dependencies_complete(&self) -> bool { - self.declaration_type_dependencies_complete - } - pub fn declaration_type_call_head_dependencies( - &self, - ) -> &[DeclarationTypeCallHeadDependencyEvent] { - &self.declaration_type_call_head_dependencies - } - pub fn declaration_type_call_head_dependencies_complete(&self) -> bool { - self.declaration_type_call_head_dependencies_complete - } - pub fn declaration_builtin_type_call_head_dependencies( - &self, - ) -> &[DeclarationBuiltinTypeCallHeadDependencyEvent] { - &self.declaration_builtin_type_call_head_dependencies - } - pub fn supported_type_call_heads_complete(&self) -> bool { - self.supported_type_call_heads_complete - } - pub fn named_const_dependencies(&self) -> &[NamedConstDependencyEvent] { - &self.named_const_dependencies - } - pub fn named_value_const_dependencies_complete(&self) -> bool { - self.named_value_const_dependencies_complete - } - pub fn implicit_named_destructor_dependencies( - &self, - ) -> &[rue_air::ImplicitNamedDestructorDependencyEvent] { - &self.implicit_named_destructor_dependencies - } - pub fn implicit_named_destructor_dependencies_complete(&self) -> bool { - self.implicit_named_destructor_dependencies_complete - } pub(crate) fn body_owner_issuer(&self) -> &BoundDefinitionSet { &self.body_owner_issuer } - pub(crate) fn durable_ordinary_body_payloads(&self) -> &[crate::DurableOrdinaryBodyPayload] { - &self.durable_ordinary_body_payloads - } - - #[cfg(test)] - pub(crate) fn durable_specialized_body_payloads( - &self, - ) -> &[crate::DurableSpecializedBodyPayload] { - &self.durable_specialized_body_payloads - } - - /// Explicitly unstable equality status for durable-cache instrumentation. - pub(crate) fn unstable_durable_artifact_status( - &self, - ) -> crate::unstable::DurableArtifactStatus { - crate::unstable::DurableArtifactStatus::from_debug(&self.durable_specialized_body_payloads) - } /// Structural work performed by the request that computed this output. /// An exact-cycle caller may receive a memoized output without executing a /// new semantic request, so these fields can describe historical work from @@ -871,7 +716,7 @@ impl CanonicalSemanticOutput { /// must never revive the removed ordinary declaration resolver. pub(crate) fn analyze_prepared_canonical_program_reusing_declarations( merged: &CanonicalMergedProgram, - rir: &CanonicalRirOutput, + rir: Arc, options: &CompileOptions, _imports: &CanonicalImportGraph, prepared: CanonicalPreparedDeclarations<'_>, @@ -1487,7 +1332,7 @@ struct CanonicalBodyCompositionFailure { fn finish_canonical_analysis( input: CodegenInputDescriptor, merged: &CanonicalMergedProgram, - rir: &CanonicalRirOutput, + rir: Arc, options: &CompileOptions, declaration_index: RirDeclarationIndexWork, bound: rue_air::BoundSema<'_>, @@ -1541,7 +1386,7 @@ fn finish_canonical_analysis( fn finish_canonical_analysis_with( input: CodegenInputDescriptor, merged: &CanonicalMergedProgram, - rir: &CanonicalRirOutput, + rir: Arc, options: &CompileOptions, declaration_index: RirDeclarationIndexWork, bound: rue_air::BoundSema<'_>, @@ -1800,32 +1645,15 @@ fn finish_canonical_analysis_with( durable_body_reuse_work.reused_bodies += body_analysis.ordinary_bodies_reused; durable_body_reuse_work.skipped_body_analyses += body_analysis.ordinary_body_analyses_skipped; fold_body_import_work(&mut durable_body_reuse_work, body_analysis); - let mut durable_body_work = crate::DurableBodyWork { + let durable_body_work = crate::DurableBodyWork { export_attempts: body_analysis.ordinary_body_exports_attempted, export_successes: body_analysis.ordinary_body_exports_succeeded, export_rejections: body_analysis.ordinary_body_exports_rejected, - last_export_failure: body_analysis - .last_ordinary_body_export_failure - .or(body_analysis.last_specialized_body_export_failure), instructions_exported: body_analysis.ordinary_body_export_instructions_emitted, places_exported: body_analysis.ordinary_body_export_places_emitted, strings_exported: body_analysis.ordinary_body_export_strings_emitted, ..durable_body_reuse_work }; - let durable_ordinary_body_payloads = crate::convert_semantic_body_exports( - &sema_output.ordinary_body_exports, - merged, - &authoritative_definitions, - &mut durable_body_work, - ) - .unwrap_or_else(|_| Arc::from([])); - let durable_specialized_body_payloads = crate::convert_semantic_specialized_body_exports( - &sema_output.specialized_body_exports, - merged, - &authoritative_definitions, - &mut durable_body_work, - ) - .unwrap_or_else(|_| Arc::from([])); let anonymous_nominal_associations = sema_output .anonymous_nominal_identities_by_type .values() @@ -1861,37 +1689,6 @@ fn finish_canonical_analysis_with( anonymous_nominal_associations.sort(); let anonymous_nominal_associations: Arc<[crate::AnonymousNominalKey]> = anonymous_nominal_associations.into(); - let analyzed_body_owners = sema_output.analyzed_body_owners.clone(); - let body_named_dependencies = sema_output.body_named_dependencies.clone(); - let ordinary_free_function_dependencies = - sema_output.ordinary_free_function_dependencies.clone(); - let ordinary_free_function_dependencies_complete = - sema_output.ordinary_free_function_dependencies_complete; - let specialized_free_function_origins = sema_output.specialized_free_function_origins.clone(); - let specialized_free_function_dependencies = - sema_output.specialized_free_function_dependencies.clone(); - let specialized_free_function_dependencies_complete = - sema_output.specialized_free_function_dependencies_complete; - let named_method_dependencies = sema_output.named_method_dependencies.clone(); - let non_generic_named_method_dependencies_complete = - sema_output.non_generic_named_method_dependencies_complete; - let generic_named_method_dependencies_complete = - sema_output.generic_named_method_dependencies_complete; - let named_destructor_dependencies = sema_output.named_destructor_dependencies.clone(); - let named_destructor_dependencies_complete = sema_output.named_destructor_dependencies_complete; - let declaration_type_dependencies = sema_output.declaration_type_dependencies.clone(); - let declaration_type_dependencies_complete = sema_output.declaration_type_dependencies_complete; - let declaration_type_call_head_dependencies = - sema_output.declaration_type_call_head_dependencies.clone(); - let declaration_type_call_head_dependencies_complete = - sema_output.declaration_type_call_head_dependencies_complete; - let declaration_builtin_type_call_head_dependencies = sema_output - .declaration_builtin_type_call_head_dependencies - .clone(); - let supported_type_call_heads_complete = sema_output.supported_type_call_heads_complete; - let named_const_dependencies = sema_output.named_const_dependencies.clone(); - let named_value_const_dependencies_complete = - sema_output.named_value_const_dependencies_complete; let issue_type = |stable: &crate::TypeInstanceKey| { stable.try_map_identities( &|definition| authoritative_definitions.semantic_token_for_key(definition), @@ -2118,15 +1915,6 @@ fn finish_canonical_analysis_with( // runs outside both `sema` and `cfg_construction`, so it needs its own leaf // for the pipeline residual to stay honest (RUE-786). let _finalization_span = info_span!("semantic_finalization").entered(); - let durable_specialized_body_payloads = - crate::durable_body::attach_specialized_implicit_drop_dependencies( - durable_specialized_body_payloads, - &cfg.implicit_named_destructor_dependencies, - merged, - &authoritative_definitions, - &mut durable_body_work, - ) - .unwrap_or_else(|_| Arc::from([])); let mut warnings = cfg.warnings; warnings.sort_by(|left, right| { let key = |warning: &CompileWarning| { @@ -2164,39 +1952,14 @@ fn finish_canonical_analysis_with( }; Ok(CanonicalSemanticOutput { input, + rir, functions: cfg.functions, type_pool: cfg.type_pool, strings: cfg.strings, warnings, anonymous_nominal_associations, body_owner_issuer: authoritative_definitions, - durable_ordinary_body_payloads, - durable_specialized_body_payloads, work, - analyzed_body_owners, - body_named_dependencies, - ordinary_free_function_dependencies, - ordinary_free_function_dependencies_complete, - specialized_free_function_origins, - specialized_free_function_dependencies, - specialized_free_function_dependencies_complete, - named_method_dependencies, - non_generic_named_method_dependencies_complete, - generic_named_method_dependencies_complete, - named_destructor_dependencies, - named_destructor_dependencies_complete, - declaration_type_dependencies, - declaration_type_dependencies_complete, - declaration_type_call_head_dependencies, - declaration_type_call_head_dependencies_complete, - declaration_builtin_type_call_head_dependencies, - supported_type_call_heads_complete, - named_const_dependencies, - named_value_const_dependencies_complete, - implicit_named_destructor_dependencies: cfg.implicit_named_destructor_dependencies, - implicit_named_destructor_dependencies_complete: cfg - .implicit_named_destructor_dependencies_complete, - body_references: BTreeMap::new(), }) } @@ -2332,7 +2095,7 @@ mod tests { session.update(snapshot).into_result().unwrap(); } let output = session.canonical_semantic(options).unwrap(); - let rir = session.selected_semantic_rir_owner().unwrap(); + let rir = output.rir_owner().clone(); (output, rir) } @@ -2593,155 +2356,6 @@ mod tests { assert!(many.binding.indexed_free_functions > one.binding.indexed_free_functions); } - fn named_method_capture_with_irrelevant_declarations( - count: usize, - ) -> (Arc, Arc) { - let mut source = String::from( - "fn helper() -> i32 { 1 } struct Value { fn run(borrow self) -> i32 { helper() } } fn main() -> i32 { let value = Value {}; value.run() }", - ); - for index in 0..count { - source.push_str(&format!(" fn irrelevant{index}() -> i32 {{ {index} }}")); - } - let snapshot = snapshot(&[(1, "/main.rue", "main.rue", &source)], 1); - canonical(&snapshot, &CompileOptions::default()) - } - - #[test] - fn named_method_capture_is_unchanged_by_irrelevant_declarations() { - let (one, one_rir) = named_method_capture_with_irrelevant_declarations(1); - let (many, many_rir) = named_method_capture_with_irrelevant_declarations(128); - assert_eq!( - function_fingerprint(one.functions(), one_rir.semantic_symbols().interner()), - function_fingerprint(many.functions(), many_rir.semantic_symbols().interner()) - ); - - for output in [&one, &many] { - let method = output - .functions() - .iter() - .find(|function| { - matches!( - &function.semantic_identity, - crate::FunctionInstanceKey::Definition(definition) - if definition.kind() == crate::StableDefinitionKind::Method - && definition.name() == "run" - && definition.owner().is_some_and(|owner| owner.name() == "Value") - ) - }) - .expect("reachable named method must be a production body"); - let references = output - .body_references(&method.semantic_identity) - .expect("production body must retain its exact references"); - assert!(references.0.iter().any(|reference| matches!( - reference, - crate::body_query::BodyReference::Callable( - crate::FunctionInstanceKey::Definition(definition) - ) if definition.name() == "helper" - ))); - } - } - - #[test] - fn comptime_named_methods_are_single_runtime_bodies_not_specializations() { - let source = snapshot( - &[( - 1, - "/main.rue", - "main.rue", - "fn helper() -> i32 { 1 } struct Value { fn choose(borrow self, comptime n: i32) -> i32 { helper() + n } } fn main() -> i32 { let value = Value {}; value.choose(1) + value.choose(2) }", - )], - 1, - ); - let (output, _) = canonical(&source, &CompileOptions::default()); - assert_eq!( - output - .functions() - .iter() - .filter(|function| matches!( - &function.semantic_identity, - crate::FunctionInstanceKey::Definition(definition) - if definition.kind() == crate::StableDefinitionKind::Method - && definition.name() == "choose" - && definition.owner().is_some_and(|owner| owner.name() == "Value") - )) - .count(), - 1, - "{:#?}", - output - .functions() - .iter() - .map(|function| &function.semantic_identity) - .collect::>() - ); - assert!( - output.functions().iter().all(|function| !matches!( - &function.semantic_identity, - crate::FunctionInstanceKey::Specialization { base, .. } - if function_base_definition(base).is_some_and(|definition| { - definition.kind() == crate::StableDefinitionKind::Method - && definition.name() == "choose" - }) - )), - "comptime named-method arguments do not create runtime bodies" - ); - let method = output - .functions() - .iter() - .find(|function| { - function_base_definition(&function.semantic_identity).is_some_and(|definition| { - definition.kind() == crate::StableDefinitionKind::Method - && definition.name() == "choose" - }) - }) - .unwrap(); - let method_references = output.body_references(&method.semantic_identity).unwrap(); - assert!( - method_references.0.iter().any(|reference| matches!( - reference, - crate::body_query::BodyReference::Callable(identity) - if function_base_definition(identity) - .is_some_and(|definition| definition.name() == "helper") - )), - "the retained named-method body must close over its transitive helper" - ); - assert!( - output.functions().iter().any(|function| { - matches!( - &function.semantic_identity, - crate::FunctionInstanceKey::Definition(definition) - if definition.kind() == crate::StableDefinitionKind::Function - && definition.name() == "helper" - ) - }), - "the transitive helper must be composed into production output" - ); - let main = output - .functions() - .iter() - .find(|function| { - function_base_definition(&function.semantic_identity) - .is_some_and(|definition| definition.name() == "main") - }) - .unwrap(); - let references = output.body_references(&main.semantic_identity).unwrap(); - assert_eq!( - references - .0 - .iter() - .filter(|reference| matches!( - reference, - crate::body_query::BodyReference::Callable(identity) - if function_base_definition(identity).is_some_and(|definition| { - definition.kind() == crate::StableDefinitionKind::Method - && definition.name() == "choose" - }) - )) - .count(), - 1, - "both calls share the named method's one exact body reference" - ); - } - #[test] fn codegen_input_tracks_root_paths_and_options_but_not_linker() { let sources = [ diff --git a/crates/rue-compiler/src/definition_snapshot.rs b/crates/rue-compiler/src/definition_snapshot.rs index f76780efc..b6aab4876 100644 --- a/crates/rue-compiler/src/definition_snapshot.rs +++ b/crates/rue-compiler/src/definition_snapshot.rs @@ -394,6 +394,7 @@ impl DefinitionSnapshot { } #[cfg(test)] + #[allow(dead_code)] pub fn shards(&self) -> &[Arc] { &self.shards } diff --git a/crates/rue-compiler/src/diagnostic_attempt_store.rs b/crates/rue-compiler/src/diagnostic_attempt_store.rs index 23504175a..a57bea561 100644 --- a/crates/rue-compiler/src/diagnostic_attempt_store.rs +++ b/crates/rue-compiler/src/diagnostic_attempt_store.rs @@ -66,6 +66,7 @@ pub(crate) struct ImportDiagnosticInputDescriptor { } impl ImportDiagnosticInputDescriptor { + #[cfg(test)] pub(crate) fn source_revision(&self) -> &SourceRevision { &self.source } @@ -232,15 +233,22 @@ impl DiagnosticAttemptStore { } pub(crate) fn select_snapshot(&mut self, snapshot: &Arc) -> bool { - let Some(id) = self + let id = self .entries .iter() .find(|entry| Arc::ptr_eq(entry.snapshot(), snapshot)) .map(|entry| entry.id) - else { - return false; - }; + .unwrap_or_else(|| { + use std::sync::atomic::{AtomicU64, Ordering}; + + static NEXT_ID: AtomicU64 = AtomicU64::new(1 << 63); + self.push(Arc::new(SnapshotDiagnosticAttempt { + id: crate::session::AttemptId(NEXT_ID.fetch_add(1, Ordering::Relaxed)), + snapshot: snapshot.clone(), + })) + }); self.select_id(id); + self.evict(); true } @@ -330,25 +338,17 @@ impl DiagnosticAttemptStore { #[cfg(test)] pub(crate) fn select_test_snapshot(&mut self, snapshot: Arc) { - use std::sync::atomic::{AtomicU64, Ordering}; - - static NEXT_ID: AtomicU64 = AtomicU64::new(1 << 63); - self.select(Arc::new(TestDiagnosticAttempt { - id: crate::session::AttemptId(NEXT_ID.fetch_add(1, Ordering::Relaxed)), - snapshot, - })); + self.select_snapshot(&snapshot); } } -#[cfg(test)] #[derive(Debug)] -struct TestDiagnosticAttempt { +struct SnapshotDiagnosticAttempt { id: crate::session::AttemptId, snapshot: Arc, } -#[cfg(test)] -impl AttemptView for TestDiagnosticAttempt { +impl AttemptView for SnapshotDiagnosticAttempt { fn id(&self) -> crate::session::AttemptId { self.id } @@ -365,9 +365,6 @@ impl AttemptView for TestDiagnosticAttempt { fn origin_id(&self) -> crate::session::AttemptId { self.id } - fn dependencies(&self) -> &[crate::query_graph::ObservedDependency] { - &[] - } fn work(&self) -> &crate::session::QueryStructuralWork { &crate::session::QueryStructuralWork::None } @@ -435,9 +432,6 @@ mod tests { fn origin_id(&self) -> crate::session::AttemptId { self.id } - fn dependencies(&self) -> &[crate::query_graph::ObservedDependency] { - &[] - } fn work(&self) -> &crate::session::QueryStructuralWork { &crate::session::QueryStructuralWork::None } diff --git a/crates/rue-compiler/src/durable_body.rs b/crates/rue-compiler/src/durable_body.rs index d55c2d71f..3fdbbddfd 100644 --- a/crates/rue-compiler/src/durable_body.rs +++ b/crates/rue-compiler/src/durable_body.rs @@ -1,118 +1,26 @@ -//! Versioned, request-independent ordinary-body candidates. -//! -//! The compiler owns the authorization envelope while `rue-air` owns the -//! request-independent body algebra stored inside it. Compact live AIR remains -//! separate and is relocated only at export and import boundaries. +//! Body-query accounting retained by the canonical revisioned semantic runtime. -use std::{cell::RefCell, collections::BTreeSet, sync::Arc}; +use crate::StableDefinitionKey; -use rue_air::{ - SemanticBodyInstData, SemanticBodyPattern, SemanticBodyProjection, SemanticDefinitionToken, - SemanticImportConstValue, SemanticImportType, SemanticModuleToken, -}; - -use crate::{ - BoundDefinitionSet, CanonicalMergedProgram, StableBodyDependencyInputRecord, - StableDefinitionKey, StableDefinitionKind, -}; - -// Version 10/9: the body algebra gained the recorded method-reference payload -// (RUE-1128). Retained artifacts from the payload-free shape fail closed to -// ordinary analysis instead of misprojecting an empty reference set. -pub const DURABLE_ORDINARY_BODY_SCHEMA_VERSION: u32 = 10; -pub const DURABLE_SPECIALIZED_BODY_SCHEMA_VERSION: u32 = 9; - -/// Durable specialization of AIR's canonical body algebra. These aliases keep -/// compiler consumers explicit about the stable identity domain. -#[cfg(test)] -pub type DurableBodyAnchor = rue_air::SemanticBodyAnchor; +/// Stable identity-domain aliases used by the revisioned CFG projection. pub type DurableProjection = rue_air::SemanticBodyProjection; -#[cfg(test)] -pub type DurableAirInst = rue_air::SemanticBodyInst; pub type DurableAirInstData = rue_air::SemanticBodyInstData; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DurableOrdinaryBodyPayload { - pub schema_version: u32, - pub semantic_schema_version: crate::DurableSemanticSchemaVersion, - pub owner: StableDefinitionKey, - /// Exact current-revision body/signature input captured with the live - /// stable issuer. Finalization rejects a same-owner stale payload. - pub expected_inputs: crate::StableDefinitionInputFingerprint, - /// The canonical request-independent semantic body algebra. Authorization - /// remains a property of this complete versioned envelope, never of the - /// bare body value. - pub body: rue_air::SemanticBody, -} - -impl std::ops::Deref for DurableOrdinaryBodyPayload { - type Target = rue_air::SemanticBody; - - fn deref(&self) -> &Self::Target { - &self.body - } -} - -impl std::ops::DerefMut for DurableOrdinaryBodyPayload { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.body - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DurableOrdinaryBody { - pub payload: DurableOrdinaryBodyPayload, - /// Exact semantic inputs which authorize this candidate. Finalization - /// validates that its owner equals `payload.owner`. - pub inputs: StableBodyDependencyInputRecord, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DurableSpecializedBodyPayload { - pub schema_version: u32, - pub semantic_schema_version: crate::DurableSemanticSchemaVersion, - pub identity: rue_air::SemanticSpecializationIdentity, - /// The completed body uses the same durable AIR algebra as ordinary bodies. - pub body: DurableOrdinaryBodyPayload, - pub dependencies: Arc<[StableDefinitionKey]>, - pub dependency_boundary_complete: bool, -} - +/// Work performed while importing exact body facts into a revision-owned +/// semantic transaction. The payload itself stays in the revisioned runtime; +/// this is deliberately only observational accounting. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct DurableBodyWork { - pub candidate_comparisons: usize, pub candidate_fallbacks: usize, - pub specialized_mapping_attempts: usize, - pub specialized_mapping_successes: usize, - pub specialized_mapping_failures: usize, pub export_attempts: usize, pub export_successes: usize, pub export_rejections: usize, - pub last_export_failure: Option, pub instructions_exported: usize, pub places_exported: usize, pub strings_exported: usize, - pub conversion_attempts: usize, - pub conversion_completions: usize, - pub conversion_failures: usize, - pub last_conversion_failure: Option, - pub stable_key_joins: usize, - pub finalization_attempts: usize, - pub finalization_completions: usize, - pub finalization_failures: usize, - pub projection_attempts: usize, - pub projection_completions: usize, - pub projection_failures: usize, - pub last_projection_failure: Option, - pub instructions_projected: usize, - pub places_projected: usize, - pub strings_projected: usize, pub import_attempts: usize, pub import_successes: usize, pub import_failures: usize, - pub unsupported_import_fallbacks: usize, - pub structural_import_fallbacks: usize, - pub last_import_failure: Option, pub installed_instructions: usize, pub installed_places: usize, pub installed_strings: usize, @@ -122,1251 +30,11 @@ pub struct DurableBodyWork { } impl DurableBodyWork { - fn reject_projection( - &mut self, - reason: DurableBodyProjectionFailure, - ) -> Result { - self.projection_failures += 1; - self.last_projection_failure = Some(reason); - Err(reason) - } - pub(crate) fn record_import_failure( &mut self, - reason: rue_air::SemanticBodyImportFailureKind, + _reason: rue_air::SemanticBodyImportFailureKind, count: usize, ) { self.import_failures += count; - match reason { - rue_air::SemanticBodyImportFailureKind::UnsupportedForm => { - self.unsupported_import_fallbacks += count; - } - rue_air::SemanticBodyImportFailureKind::Semantic(_) - | rue_air::SemanticBodyImportFailureKind::StructuralValidation => { - self.structural_import_fallbacks += count; - } - } - self.last_import_failure = Some(reason); - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DurableBodyProjectionFailure { - SchemaVersionMismatch, - OwnerInputMismatch, - BlockedDependencyInputs, - InputFingerprintMismatch, - InvalidInstructionReference, - ForwardInstructionReference, - InvalidPlaceReference, - InvalidStringReference, - InvalidAnchor, - InvalidSourceOrder, - InvalidParameterModes, - InvalidParameterDrop, - InvalidBorrowSlot, - WarningProducingBody, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DurableBodyConversionFailure { - ForeignRevision, - ForeignOwnerToken, - MissingStableDefinition, - AmbiguousStableDefinition, - WrongDefinitionKind, - MissingDependencyInputs, - AmbiguousDependencyInputs, - OwnerInputMismatch, - BlockedDependencyInputs, - WarningProducingBody, - UnresolvedModule, - UnsupportedGenericCall, - FingerprintUnavailable, -} - -struct DefinitionJoinIndex<'a> { - definitions: &'a BoundDefinitionSet, -} - -impl<'a> DefinitionJoinIndex<'a> { - fn new(_merged: &'a CanonicalMergedProgram, definitions: &'a BoundDefinitionSet) -> Self { - Self { definitions } - } - - fn join( - &self, - identity: &SemanticDefinitionToken, - work: &mut DurableBodyWork, - ) -> Result<&'a StableDefinitionKey, DurableBodyConversionFailure> { - work.stable_key_joins += 1; - match self.definitions.key_for_semantic_token(*identity) { - Ok(key) => Ok(key), - Err(rue_air::SemanticStableResolutionFailure::Missing) => { - Err(DurableBodyConversionFailure::MissingStableDefinition) - } - Err(rue_air::SemanticStableResolutionFailure::Ambiguous) => { - Err(DurableBodyConversionFailure::AmbiguousStableDefinition) - } - Err(rue_air::SemanticStableResolutionFailure::WrongKind) => { - Err(DurableBodyConversionFailure::WrongDefinitionKind) - } - Err(rue_air::SemanticStableResolutionFailure::ForeignIssuer) => { - Err(DurableBodyConversionFailure::ForeignOwnerToken) - } - } - } -} - -fn join_definition<'a>( - identity: &SemanticDefinitionToken, - index: &DefinitionJoinIndex<'a>, - work: &mut DurableBodyWork, -) -> Result<&'a StableDefinitionKey, DurableBodyConversionFailure> { - index.join(identity, work) -} - -fn canonical_type( - ty: &SemanticImportType, - merged: &CanonicalMergedProgram, - index: &DefinitionJoinIndex<'_>, - work: &mut DurableBodyWork, -) -> Result, DurableBodyConversionFailure> -{ - Ok(match ty { - SemanticImportType::I8 => SemanticImportType::I8, - SemanticImportType::I16 => SemanticImportType::I16, - SemanticImportType::I32 => SemanticImportType::I32, - SemanticImportType::I64 => SemanticImportType::I64, - SemanticImportType::U8 => SemanticImportType::U8, - SemanticImportType::U16 => SemanticImportType::U16, - SemanticImportType::U32 => SemanticImportType::U32, - SemanticImportType::U64 => SemanticImportType::U64, - SemanticImportType::Bool => SemanticImportType::Bool, - SemanticImportType::Unit => SemanticImportType::Unit, - SemanticImportType::Never => SemanticImportType::Never, - SemanticImportType::ComptimeType => SemanticImportType::ComptimeType, - SemanticImportType::BuiltinNominal { name, kind } => { - canonical_builtin_nominal(name, *kind)? - } - SemanticImportType::Nominal(identity) => { - let key = join_definition(identity, index, work)?; - if !matches!( - key.kind(), - StableDefinitionKind::Struct | StableDefinitionKind::Enum - ) { - return Err(DurableBodyConversionFailure::WrongDefinitionKind); - } - SemanticImportType::Nominal(key.clone()) - } - SemanticImportType::AnonymousNominal(identity) => { - let work = RefCell::new(work); - let identity = identity.try_map_identities( - &|token| Ok(join_definition(token, index, &mut work.borrow_mut())?.clone()), - &|token| { - index - .definitions - .module_for_semantic_token(merged, *token) - .cloned() - .map_err(|failure| match failure { - rue_air::SemanticStableResolutionFailure::ForeignIssuer => { - DurableBodyConversionFailure::ForeignOwnerToken - } - _ => DurableBodyConversionFailure::UnresolvedModule, - }) - }, - )?; - validate_anonymous_identity(&identity)?; - SemanticImportType::AnonymousNominal(identity) - } - SemanticImportType::Array { element, len } => SemanticImportType::Array { - element: Box::new(canonical_type(element, merged, index, work)?), - len: *len, - }, - SemanticImportType::Slice { element, name } => SemanticImportType::Slice { - element: Box::new(canonical_type(element, merged, index, work)?), - name: name.clone(), - }, - SemanticImportType::PtrConst(value) => { - SemanticImportType::PtrConst(Box::new(canonical_type(value, merged, index, work)?)) - } - SemanticImportType::PtrMut(value) => { - SemanticImportType::PtrMut(Box::new(canonical_type(value, merged, index, work)?)) - } - SemanticImportType::Module(token) => SemanticImportType::Module( - index - .definitions - .module_for_semantic_token(merged, *token) - .map_err(|failure| match failure { - rue_air::SemanticStableResolutionFailure::ForeignIssuer => { - DurableBodyConversionFailure::ForeignOwnerToken - } - _ => DurableBodyConversionFailure::UnresolvedModule, - })? - .clone(), - ), - SemanticImportType::GenericParameter(index) => SemanticImportType::GenericParameter(*index), - }) -} - -fn validate_anonymous_identity( - identity: &rue_air::AnonymousNominalKey, -) -> Result<(), DurableBodyConversionFailure> { - use rue_air::{CanonicalArgumentValue as V, StableProducerId as P}; - - fn type_key( - value: &rue_air::TypeInstanceKey, - ) -> Result<(), DurableBodyConversionFailure> { - use rue_air::{NominalInstanceKey as N, TypeInstanceKey as T}; - match value { - T::Nominal(N::Named(key)) - if !matches!( - key.kind(), - StableDefinitionKind::Struct | StableDefinitionKind::Enum - ) => - { - Err(DurableBodyConversionFailure::WrongDefinitionKind) - } - T::Nominal(N::Anonymous(key)) => validate_anonymous_identity(key), - T::Array { element, .. } | T::PtrConst(element) | T::PtrMut(element) => { - type_key(element) - } - _ => Ok(()), - } - } - fn function_key( - value: &rue_air::FunctionInstanceKey, - ) -> Result<(), DurableBodyConversionFailure> { - use rue_air::FunctionInstanceKey as F; - match value { - F::Definition(key) if !key.kind().owns_body() => { - Err(DurableBodyConversionFailure::WrongDefinitionKind) - } - F::Specialization { base, arguments } => { - function_key(base)?; - arguments_key(arguments) - } - F::AnonymousMember { owner, .. } | F::DropGlue(owner) => type_key(owner), - _ => Ok(()), - } - } - fn arguments_key( - arguments: &rue_air::CanonicalArguments, - ) -> Result<(), DurableBodyConversionFailure> { - for value in arguments.types.iter() { - type_key(value)?; - } - for value in arguments.values.iter() { - match value { - V::Type(value) => type_key(value)?, - V::Function(value) => function_key(value)?, - _ => {} - } - } - Ok(()) - } - - match &identity.producer { - P::Definition(_) => {} - P::Function(value) => function_key(value)?, - } - arguments_key(&identity.arguments) -} - -fn canonical_builtin_nominal( - name: &Arc, - kind: rue_air::SemanticImportNominalKind, -) -> Result, DurableBodyConversionFailure> -{ - if crate::durable_semantics::builtin_nominal_kind(name) != Some(kind) { - return Err(DurableBodyConversionFailure::WrongDefinitionKind); - } - Ok(SemanticImportType::BuiltinNominal { - name: name.clone(), - kind, - }) -} - -fn canonical_const_value( - value: &SemanticImportConstValue, - merged: &CanonicalMergedProgram, - index: &DefinitionJoinIndex<'_>, - work: &mut DurableBodyWork, -) -> Result< - SemanticImportConstValue, - DurableBodyConversionFailure, -> { - Ok(match value { - SemanticImportConstValue::Integer(value) => SemanticImportConstValue::Integer(*value), - SemanticImportConstValue::Bool(value) => SemanticImportConstValue::Bool(*value), - SemanticImportConstValue::Type(value) => { - SemanticImportConstValue::Type(canonical_type(value, merged, index, work)?) - } - SemanticImportConstValue::Function(value) => { - let key = join_definition(value, index, work)?; - if !matches!( - key.kind(), - StableDefinitionKind::Function - | StableDefinitionKind::Method - | StableDefinitionKind::AssociatedFunction - ) { - return Err(DurableBodyConversionFailure::WrongDefinitionKind); - } - SemanticImportConstValue::Function(key.clone()) - } - SemanticImportConstValue::Unit => SemanticImportConstValue::Unit, - SemanticImportConstValue::String(value) => SemanticImportConstValue::String(value.clone()), - }) -} - -fn durable_specialization_identity( - identity: &rue_air::SemanticSpecializationIdentity< - SemanticDefinitionToken, - SemanticModuleToken, - >, - merged: &CanonicalMergedProgram, - index: &DefinitionJoinIndex<'_>, - work: &mut DurableBodyWork, -) -> Result< - rue_air::SemanticSpecializationIdentity, - DurableBodyConversionFailure, -> { - let base = join_definition(&identity.base, index, work)?.clone(); - if base.kind() != StableDefinitionKind::Function { - return Err(DurableBodyConversionFailure::WrongDefinitionKind); - } - Ok(rue_air::SemanticSpecializationIdentity { - base, - type_arguments: identity - .type_arguments - .iter() - .map(|value| canonical_type(value, merged, index, work)) - .collect::, _>>()? - .into(), - value_arguments: identity - .value_arguments - .iter() - .map(|value| canonical_const_value(value, merged, index, work)) - .collect::, _>>()? - .into(), - }) -} - -fn canonical_identity_dependency_keys( - identity: &rue_air::SemanticSpecializationIdentity, -) -> BTreeSet { - fn visit_type( - value: &SemanticImportType, - keys: &mut BTreeSet, - ) { - match value { - SemanticImportType::Nominal(key) => { - keys.insert(key.clone()); - } - SemanticImportType::AnonymousNominal(identity) => { - collect_anonymous_definition_keys(identity, keys); - } - SemanticImportType::Array { element, .. } - | SemanticImportType::PtrConst(element) - | SemanticImportType::PtrMut(element) => visit_type(element, keys), - _ => {} - } - } - let mut keys = BTreeSet::new(); - for value in identity.type_arguments.iter() { - visit_type(value, &mut keys); - } - for value in identity.value_arguments.iter() { - match value { - SemanticImportConstValue::Type(value) => visit_type(value, &mut keys), - SemanticImportConstValue::Function(key) => { - keys.insert(key.clone()); - } - _ => {} - } - } - keys -} - -fn collect_anonymous_definition_keys( - identity: &rue_air::AnonymousNominalKey, - keys: &mut BTreeSet, -) { - let keys = RefCell::new(keys); - identity - .try_map_identities( - &|key| { - keys.borrow_mut().insert(key.clone()); - Ok::<_, std::convert::Infallible>(key.clone()) - }, - &|module| Ok::<_, std::convert::Infallible>(module.clone()), - ) - .expect("identity-preserving anonymous dependency traversal is infallible"); -} - -fn collect_nominal_definition_keys( - identity: &rue_air::NominalInstanceKey, - keys: &mut BTreeSet, -) { - match identity { - rue_air::NominalInstanceKey::Builtin { .. } => {} - rue_air::NominalInstanceKey::Named(key) => { - keys.insert(key.clone()); - } - rue_air::NominalInstanceKey::Anonymous(key) => { - collect_anonymous_definition_keys(key, keys); - } - } -} - -fn collect_function_definition_keys( - identity: &rue_air::FunctionInstanceKey, - keys: &mut BTreeSet, -) { - let keys = RefCell::new(keys); - identity - .try_map_identities( - &|key| { - keys.borrow_mut().insert(key.clone()); - Ok::<_, std::convert::Infallible>(key.clone()) - }, - &|module| Ok::<_, std::convert::Infallible>(module.clone()), - ) - .expect("identity-preserving function dependency traversal is infallible"); -} - -fn record_specialized_conversion_failure( - work: &mut DurableBodyWork, - failure: DurableBodyConversionFailure, -) -> DurableBodyConversionFailure { - work.conversion_attempts += 1; - work.conversion_failures += 1; - work.last_conversion_failure = Some(failure); - work.atomic_discards += 1; - failure -} - -/// Atomically joins completed specialization identities and bodies to stable -/// compiler-owned values. A failed identity, argument, or body conversion -/// discards the whole export set, never a partially stable artifact. -pub fn convert_semantic_specialized_body_exports( - exports: &[rue_air::SemanticSpecializedBodyExport], - merged: &CanonicalMergedProgram, - definitions: &BoundDefinitionSet, - work: &mut DurableBodyWork, -) -> Result, DurableBodyConversionFailure> { - if definitions.source_revision() != merged.ast().source_revision() { - work.conversion_attempts += exports.len(); - work.conversion_failures += exports.len(); - work.atomic_discards += usize::from(!exports.is_empty()); - return Err(DurableBodyConversionFailure::ForeignRevision); - } - let index = DefinitionJoinIndex::new(merged, definitions); - let mut converted = Vec::with_capacity(exports.len()); - for export in exports { - let preflight = (|| { - let identity = durable_specialization_identity(&export.identity, merged, &index, work)?; - let base = identity.base.clone(); - let token = definitions - .body_owner_endpoints() - .into_iter() - .find(|endpoint| definitions.key_for_body_token(endpoint.token).ok() == Some(&base)) - .map(|endpoint| endpoint.token) - .ok_or(DurableBodyConversionFailure::MissingStableDefinition)?; - let mut dependencies = export - .dependencies - .iter() - .map(|dependency| join_definition(dependency, &index, work).cloned()) - .collect::, _>>()?; - dependencies.extend(canonical_identity_dependency_keys(&identity)); - Ok((identity, token, dependencies)) - })(); - let (identity, token, dependencies) = match preflight { - Ok(preflight) => preflight, - Err(failure) => { - return Err(record_specialized_conversion_failure(work, failure)); - } - }; - let ordinary = rue_air::SemanticBodyExport { - owner: token, - body: export.body.clone(), - }; - let bodies = convert_semantic_body_exports(&[ordinary], merged, definitions, work)?; - let [body] = bodies.as_ref() else { - unreachable!("one successful body conversion returns one body") - }; - converted.push(DurableSpecializedBodyPayload { - schema_version: DURABLE_SPECIALIZED_BODY_SCHEMA_VERSION, - semantic_schema_version: crate::DURABLE_SEMANTIC_SCHEMA_VERSION, - identity, - body: body.clone(), - dependencies: dependencies.into_iter().collect::>().into(), - dependency_boundary_complete: export.dependency_boundary_complete, - }); - } - Ok(converted.into()) -} - -/// Attach destructor inputs observed by CFG lowering to the exact completed -/// specialization that owned the drop. No dependency is inferred from types -/// merely mentioned or borrowed by the body. -pub fn attach_specialized_implicit_drop_dependencies( - payloads: Arc<[DurableSpecializedBodyPayload]>, - events: &[rue_air::ImplicitNamedDestructorDependencyEvent], - merged: &CanonicalMergedProgram, - definitions: &BoundDefinitionSet, - work: &mut DurableBodyWork, -) -> Result, DurableBodyConversionFailure> { - let index = DefinitionJoinIndex::new(merged, definitions); - let mut payloads = payloads.to_vec(); - for event in events { - let rue_air::ImplicitDropDependencySourceEvent::Specialization { identity } = &event.source - else { - continue; - }; - let identity = durable_specialization_identity(identity, merged, &index, work)?; - let Some(payload) = payloads - .iter_mut() - .find(|payload| payload.identity == identity) - else { - // Warning-producing and otherwise rejected exports have no durable - // payload to enrich. - continue; - }; - work.stable_key_joins += 1; - let destructor = definitions - .definitions() - .iter() - .find(|record| { - let key = record.stable_key(); - key.kind() == rue_air::StableDefinitionKind::Destructor - && key.owner().map(|owner| owner.name()) - == Some(event.target_owner_name.as_str()) - && record.declaration_span().file_id.index() == event.target_file - }) - .map(|record| record.stable_key().clone()) - .ok_or(DurableBodyConversionFailure::MissingStableDefinition)?; - let mut dependencies = payload - .dependencies - .iter() - .cloned() - .collect::>(); - dependencies.insert(destructor); - payload.dependencies = dependencies.into_iter().collect::>().into(); - } - Ok(payloads.into()) -} - -/// Atomically join AIR's neutral ordinary-body exports to the authoritative -/// stable-definition universe for this exact revision. Complete dependency -/// inputs are attached later by [`finalize_durable_ordinary_bodies`]. -pub fn convert_semantic_body_exports( - exports: &[rue_air::SemanticBodyExport], - merged: &CanonicalMergedProgram, - definitions: &BoundDefinitionSet, - work: &mut DurableBodyWork, -) -> Result, DurableBodyConversionFailure> { - if definitions.source_revision() != merged.ast().source_revision() { - work.conversion_failures += exports.len(); - work.atomic_discards += usize::from(!exports.is_empty()); - return Err(DurableBodyConversionFailure::ForeignRevision); - } - - let join_index = DefinitionJoinIndex::new(merged, definitions); - let mut converted = Vec::with_capacity(exports.len()); - for export in exports { - work.conversion_attempts += 1; - let result = - (|| { - let owner_record = definitions - .definition_for_body_token(export.owner) - .map_err(|_| DurableBodyConversionFailure::ForeignOwnerToken)?; - let owner = owner_record.stable_key().clone(); - if !matches!( - owner.kind(), - StableDefinitionKind::Function - | StableDefinitionKind::Method - | StableDefinitionKind::AssociatedFunction - | StableDefinitionKind::Destructor - ) { - return Err(DurableBodyConversionFailure::WrongDefinitionKind); - } - if !export.body.warnings.is_empty() { - return Err(DurableBodyConversionFailure::WarningProducingBody); - } - if export.body.instructions.iter().any(|instruction| { - matches!(instruction.data, SemanticBodyInstData::CallGeneric) - }) { - return Err(DurableBodyConversionFailure::UnsupportedGenericCall); - } - - let expected_inputs = crate::session::stable_definition_input_fingerprint( - merged.definitions().source_snapshot(), - owner_record, - ) - .map_err(|_| DurableBodyConversionFailure::FingerprintUnavailable)?; - let stable_key_joins = std::cell::Cell::new(0usize); - let body = export.body.try_map_keys( - &|identity| { - stable_key_joins.set(stable_key_joins.get() + 1); - join_index - .definitions - .key_for_semantic_token(*identity) - .cloned() - .map_err(|failure| match failure { - rue_air::SemanticStableResolutionFailure::Missing => { - DurableBodyConversionFailure::MissingStableDefinition - } - rue_air::SemanticStableResolutionFailure::Ambiguous => { - DurableBodyConversionFailure::AmbiguousStableDefinition - } - rue_air::SemanticStableResolutionFailure::WrongKind => { - DurableBodyConversionFailure::WrongDefinitionKind - } - rue_air::SemanticStableResolutionFailure::ForeignIssuer => { - DurableBodyConversionFailure::ForeignOwnerToken - } - }) - }, - &|token| { - join_index - .definitions - .module_for_semantic_token(merged, *token) - .cloned() - .map_err(|failure| match failure { - rue_air::SemanticStableResolutionFailure::ForeignIssuer => { - DurableBodyConversionFailure::ForeignOwnerToken - } - _ => DurableBodyConversionFailure::UnresolvedModule, - }) - }, - ); - work.stable_key_joins += stable_key_joins.get(); - let body = body?; - Ok(DurableOrdinaryBodyPayload { - schema_version: DURABLE_ORDINARY_BODY_SCHEMA_VERSION, - semantic_schema_version: crate::DURABLE_SEMANTIC_SCHEMA_VERSION, - owner, - expected_inputs, - body, - }) - })(); - - match result { - Ok(payload) => { - work.conversion_completions += 1; - converted.push(payload); - } - Err(error) => { - work.conversion_failures += 1; - work.atomic_discards += 1; - work.last_conversion_failure = Some(error); - return Err(error); - } - } - } - Ok(converted.into()) -} - -/// Attach the complete dependency records after the session has built them. -/// This stage is all-or-nothing and never mutates canonical semantic work. -pub fn finalize_durable_ordinary_bodies( - payloads: &[DurableOrdinaryBodyPayload], - inputs: &[StableBodyDependencyInputRecord], - work: &mut DurableBodyWork, -) -> Result, DurableBodyConversionFailure> { - let mut result = Vec::with_capacity(payloads.len()); - for payload in payloads { - work.finalization_attempts += 1; - let matches = inputs - .iter() - .filter(|input| input.owner() == &payload.owner) - .collect::>(); - let input = match matches.as_slice() { - [input] => (*input).clone(), - [] => { - work.finalization_failures += 1; - work.atomic_discards += 1; - return Err(DurableBodyConversionFailure::MissingDependencyInputs); - } - _ => { - work.finalization_failures += 1; - work.atomic_discards += 1; - return Err(DurableBodyConversionFailure::AmbiguousDependencyInputs); - } - }; - if input.owner() != &payload.owner { - work.finalization_failures += 1; - work.atomic_discards += 1; - return Err(DurableBodyConversionFailure::OwnerInputMismatch); - } - if input.fingerprint() != &payload.expected_inputs { - work.finalization_failures += 1; - work.atomic_discards += 1; - return Err(DurableBodyConversionFailure::OwnerInputMismatch); - } - if !input.reusable_boundary_supported() { - work.finalization_failures += 1; - work.atomic_discards += 1; - return Err(DurableBodyConversionFailure::BlockedDependencyInputs); - } - result.push(DurableOrdinaryBody { - payload: payload.clone(), - inputs: input, - }); - work.finalization_completions += 1; - } - Ok(result.into()) -} - -impl DurableOrdinaryBody { - pub fn owner(&self) -> &StableDefinitionKey { - &self.payload.owner - } - - pub fn project_semantic_body( - &self, - work: &mut DurableBodyWork, - ) -> Result>, DurableBodyProjectionFailure> - { - if self.inputs.owner() != &self.payload.owner - || self.inputs.fingerprint() != &self.payload.expected_inputs - { - work.projection_attempts += 1; - return work.reject_projection(DurableBodyProjectionFailure::InputFingerprintMismatch); - } - self.payload.project_semantic_body(work) - } -} - -impl DurableOrdinaryBodyPayload { - pub(crate) fn referenced_definition_keys(&self) -> BTreeSet { - fn visit_type( - value: &SemanticImportType, - keys: &mut BTreeSet, - ) { - match value { - SemanticImportType::Nominal(key) => { - keys.insert(key.clone()); - } - SemanticImportType::AnonymousNominal(identity) => { - collect_anonymous_definition_keys(identity, keys); - } - SemanticImportType::Array { element, .. } - | SemanticImportType::PtrConst(element) - | SemanticImportType::PtrMut(element) => visit_type(element, keys), - _ => {} - } - } - - fn visit_specialization( - identity: &rue_air::SemanticSpecializationIdentity< - StableDefinitionKey, - crate::ModuleId, - >, - keys: &mut BTreeSet, - ) { - keys.insert(identity.base.clone()); - for value in identity.type_arguments.iter() { - visit_type(value, keys); - } - for value in identity.value_arguments.iter() { - match value { - SemanticImportConstValue::Type(value) => visit_type(value, keys), - SemanticImportConstValue::Function(key) => { - keys.insert(key.clone()); - } - _ => {} - } - } - } - - let mut keys = BTreeSet::new(); - visit_type(&self.body.return_type, &mut keys); - for instruction in self.body.instructions.iter() { - visit_type(&instruction.ty, &mut keys); - match &instruction.data { - SemanticBodyInstData::TypeConst(value) - | SemanticBodyInstData::IntCast { from_ty: value, .. } => { - visit_type(value, &mut keys); - } - SemanticBodyInstData::Call { function, .. } => { - collect_function_definition_keys(function, &mut keys); - } - SemanticBodyInstData::CallSpecialized { identity, .. } => { - visit_specialization(identity, &mut keys); - } - SemanticBodyInstData::StructInit { struct_key, .. } => { - collect_nominal_definition_keys(struct_key, &mut keys); - } - SemanticBodyInstData::EnumVariant { enum_key, .. } - | SemanticBodyInstData::EnumPayloadGet { enum_key, .. } => { - collect_nominal_definition_keys(enum_key, &mut keys); - } - SemanticBodyInstData::Match { arms, .. } => { - for arm in arms.iter() { - if let SemanticBodyPattern::EnumVariant { enum_key, .. } = &arm.pattern { - collect_nominal_definition_keys(enum_key, &mut keys); - } - } - } - _ => {} - } - } - for place in self.body.places.iter() { - visit_type(&place.base_type, &mut keys); - for projection in place.projections.iter() { - match projection { - SemanticBodyProjection::Field { struct_key, .. } => { - collect_nominal_definition_keys(struct_key, &mut keys); - } - SemanticBodyProjection::Index { array_type, .. } => { - visit_type(array_type, &mut keys); - } - } - } - } - for (_, value) in self.body.param_drops.iter() { - visit_type(value, &mut keys); - } - keys - } - - pub fn project_semantic_body( - &self, - work: &mut DurableBodyWork, - ) -> Result>, DurableBodyProjectionFailure> - { - work.projection_attempts += 1; - if self.schema_version != DURABLE_ORDINARY_BODY_SCHEMA_VERSION - || !crate::DURABLE_SEMANTIC_SCHEMA_VERSION.accepts(self.semantic_schema_version) - { - return work.reject_projection(DurableBodyProjectionFailure::SchemaVersionMismatch); - } - if self.body.param_by_ref.len() != self.body.num_param_slots as usize - || self.body.param_writable.len() != self.body.num_param_slots as usize - || self - .body - .param_writable - .iter() - .zip(self.body.param_by_ref.iter()) - .any(|(writable, by_ref)| *writable && !*by_ref) - { - return work.reject_projection(DurableBodyProjectionFailure::InvalidParameterModes); - } - if self - .body - .param_drops - .iter() - .any(|(slot, _)| *slot >= self.body.num_param_slots) - { - return work.reject_projection(DurableBodyProjectionFailure::InvalidParameterDrop); - } - if self - .body - .borrow_slots - .iter() - .any(|slot| *slot >= self.body.num_locals) - { - return work.reject_projection(DurableBodyProjectionFailure::InvalidBorrowSlot); - } - let instruction_len = self.body.instructions.len(); - let place_len = self.body.places.len(); - for place in self.body.places.iter() { - for projection in place.projections.iter() { - if let SemanticBodyProjection::Index { index, .. } = projection - && *index as usize >= instruction_len - { - return work.reject_projection( - DurableBodyProjectionFailure::InvalidInstructionReference, - ); - } - } - } - for (current, instruction) in self.body.instructions.iter().enumerate() { - if instruction.anchor.start > instruction.anchor.end { - return work.reject_projection(DurableBodyProjectionFailure::InvalidAnchor); - } - let check = |value: rue_air::SemanticBodyRef| { - let index = value as usize; - if index >= instruction_len { - Err(DurableBodyProjectionFailure::InvalidInstructionReference) - } else if index >= current { - Err(DurableBodyProjectionFailure::ForwardInstructionReference) - } else { - Ok(()) - } - }; - let check_all = |values: &[rue_air::SemanticBodyRef]| -> Result<_, _> { - values.iter().try_for_each(|value| check(*value)) - }; - use SemanticBodyInstData as D; - let validated = match &instruction.data { - D::Const(_) - | D::BoolConst(_) - | D::UnitConst - | D::TypeConst(_) - | D::Break - | D::Continue - | D::Load { .. } - | D::Param { .. } - | D::StorageLive { .. } - | D::StorageDead { .. } => Ok(()), - D::CallGeneric => Err(DurableBodyProjectionFailure::InvalidInstructionReference), - D::StringConst(index) => { - if *index as usize >= self.body.strings.len() { - Err(DurableBodyProjectionFailure::InvalidStringReference) - } else { - Ok(()) - } - } - D::Add(a, b) - | D::Sub(a, b) - | D::Mul(a, b) - | D::WrappingAdd(a, b) - | D::WrappingSub(a, b) - | D::WrappingMul(a, b) - | D::Div(a, b) - | D::Mod(a, b) - | D::Eq(a, b) - | D::Ne(a, b) - | D::Lt(a, b) - | D::Gt(a, b) - | D::Le(a, b) - | D::Ge(a, b) - | D::And(a, b) - | D::Or(a, b) - | D::BitAnd(a, b) - | D::BitOr(a, b) - | D::BitXor(a, b) - | D::Shl(a, b) - | D::Shr(a, b) => check(*a).and_then(|()| check(*b)), - D::Neg(value) - | D::Not(value) - | D::BitNot(value) - | D::Drop { value } - | D::IntCast { value, .. } => check(*value), - D::Branch { - cond, - then_value, - else_value, - } => check(*cond) - .and_then(|()| check(*then_value)) - .and_then(|()| else_value.map_or(Ok(()), &check)), - D::Loop { cond, body } => check(*cond).and_then(|()| check(*body)), - D::InfiniteLoop { body } => check(*body), - D::Match { scrutinee, arms } => { - check(*scrutinee).and_then(|()| arms.iter().try_for_each(|arm| check(arm.body))) - } - D::Alloc { init, .. } => check(*init), - D::Store { value, .. } | D::ParamStore { value, .. } => check(*value), - D::Ret(value) => value.map_or(Ok(()), &check), - D::Call { args, .. } - | D::RuntimeCall { args, .. } - | D::CallSpecialized { args, .. } - | D::Intrinsic { args, .. } => args.iter().try_for_each(|arg| check(arg.value)), - D::Block { statements, value } => { - check_all(statements).and_then(|()| check(*value)) - } - D::StructInit { - fields, - source_order, - .. - } => { - let mut seen = vec![false; fields.len()]; - if fields.len() != source_order.len() - || source_order.iter().any(|index| { - let index = *index as usize; - index >= fields.len() || std::mem::replace(&mut seen[index], true) - }) - { - Err(DurableBodyProjectionFailure::InvalidSourceOrder) - } else { - check_all(fields) - } - } - D::EnumPayloadGet { base, .. } => check(*base), - D::ArrayInit { elements } - | D::EnumVariant { - payload: elements, .. - } => check_all(elements), - D::PlaceRead { place } => { - if *place as usize >= place_len { - Err(DurableBodyProjectionFailure::InvalidPlaceReference) - } else { - Ok(()) - } - } - D::PlaceWrite { place, value } => { - if *place as usize >= place_len { - Err(DurableBodyProjectionFailure::InvalidPlaceReference) - } else { - check(*value) - } - } - D::MarkMoved { value, place, .. } => { - if place.is_some_and(|place| place as usize >= place_len) { - Err(DurableBodyProjectionFailure::InvalidPlaceReference) - } else { - check(*value) - } - } - }; - if let Err(error) = validated { - return work.reject_projection(error); - } - } - if !self.body.warnings.is_empty() { - return work.reject_projection(DurableBodyProjectionFailure::WarningProducingBody); - } - let body = self - .body - .try_map_keys( - &|key| Ok::<_, std::convert::Infallible>(key.clone()), - &|module| Ok::<_, std::convert::Infallible>(Arc::from(module.as_str())), - ) - .expect("canonical body key projection is infallible"); - work.projection_completions += 1; - work.instructions_projected += body.instructions.len(); - work.places_projected += body.places.len(); - work.strings_projected += body.strings.len(); - Ok(body) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ModuleId, StableDefinitionNamespace}; - - fn owner() -> StableDefinitionKey { - StableDefinitionKey::for_test( - ModuleId::from_logical_path("main.rue").unwrap(), - StableDefinitionNamespace::Value, - StableDefinitionKind::Function, - "main", - None, - ) - } - - fn payload(instructions: Vec) -> DurableOrdinaryBodyPayload { - let key = owner(); - DurableOrdinaryBodyPayload { - schema_version: DURABLE_ORDINARY_BODY_SCHEMA_VERSION, - semantic_schema_version: crate::DURABLE_SEMANTIC_SCHEMA_VERSION, - owner: key.clone(), - expected_inputs: crate::StableDefinitionInputFingerprint { - schema_version: 1, - key, - declaration: crate::StableDefinitionFingerprint::for_test(1), - signature: crate::StableDefinitionFingerprint::for_test(2), - body_or_initializer: Some(crate::StableDefinitionFingerprint::for_test(3)), - precision: crate::StableDefinitionFingerprintPrecision::SignatureAndBody, - }, - body: rue_air::SemanticBody { - return_type: SemanticImportType::Unit, - instructions: instructions.into(), - places: Arc::from([]), - strings: Arc::from([]), - local_atoms: Arc::from([]), - param_drops: Arc::from([]), - borrow_slots: Arc::from([]), - num_locals: 0, - num_param_slots: 0, - param_by_ref: Arc::from([]), - param_writable: Arc::from([]), - allow_unreachable_code: false, - warnings: Arc::from([]), - method_references: Arc::from([]), - }, - } - } - - fn instruction(data: DurableAirInstData) -> DurableAirInst { - DurableAirInst { - data, - ty: SemanticImportType::Unit, - anchor: DurableBodyAnchor { start: 0, end: 0 }, - } - } - - #[test] - fn canonical_specialization_identity_preserves_all_argument_kinds() { - let function = StableDefinitionKey::for_test( - ModuleId::from_logical_path("helpers.rue").unwrap(), - StableDefinitionNamespace::Value, - StableDefinitionKind::Function, - "helper", - None, - ); - let identity: rue_air::SemanticSpecializationIdentity = - rue_air::SemanticSpecializationIdentity { - base: owner(), - type_arguments: Arc::from([SemanticImportType::I32]), - value_arguments: Arc::from([ - SemanticImportConstValue::Bool(true), - SemanticImportConstValue::Type(SemanticImportType::I64), - SemanticImportConstValue::Function(function.clone()), - SemanticImportConstValue::Unit, - ]), - }; - assert_eq!(identity.base, owner()); - assert_eq!(identity.type_arguments.as_ref(), &[SemanticImportType::I32]); - assert_eq!( - identity.value_arguments.as_ref(), - &[ - SemanticImportConstValue::Bool(true), - SemanticImportConstValue::Type(SemanticImportType::I64), - SemanticImportConstValue::Function(function), - SemanticImportConstValue::Unit, - ] - ); - } - - #[test] - fn projection_rejects_forward_instruction_references() { - let candidate = payload(vec![instruction(DurableAirInstData::Ret(Some(0)))]); - let mut work = DurableBodyWork::default(); - assert_eq!( - candidate.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::ForwardInstructionReference) - ); - assert_eq!(work.projection_attempts, 1); - assert_eq!(work.projection_completions, 0); - assert_eq!(work.projection_failures, 1); - } - - #[test] - fn projection_reports_retained_warning_metadata_accurately() { - let mut candidate = payload(vec![]); - candidate.body.warnings = Arc::from([rue_air::SemanticBodyWarning { - kind: rue_error::WarningKind::UnreachableCode, - anchor: DurableBodyAnchor { start: 0, end: 0 }, - labels: Arc::new([]), - notes: Arc::new([]), - helps: Arc::new([]), - suggestions: Arc::new([]), - }]); - let mut work = DurableBodyWork::default(); - assert_eq!( - candidate.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::WarningProducingBody) - ); - assert_eq!(work.projection_attempts, 1); - assert_eq!(work.projection_completions, 0); - assert_eq!(work.projection_failures, 1); - } - - #[test] - fn body_projection_rejects_a_foreign_canonical_algebra_epoch() { - let mut candidate = payload(vec![instruction(DurableAirInstData::UnitConst)]); - candidate.semantic_schema_version.implementation_epoch += 1; - let mut work = DurableBodyWork::default(); - assert_eq!( - candidate.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::SchemaVersionMismatch) - ); - assert_eq!(work.projection_failures, 1); - assert_eq!( - work.last_projection_failure, - Some(DurableBodyProjectionFailure::SchemaVersionMismatch) - ); - } - - #[test] - fn specialized_preflight_failures_are_counted_without_false_completion() { - for failure in [ - DurableBodyConversionFailure::MissingStableDefinition, - DurableBodyConversionFailure::ForeignOwnerToken, - DurableBodyConversionFailure::AmbiguousStableDefinition, - ] { - let mut work = DurableBodyWork::default(); - assert_eq!( - record_specialized_conversion_failure(&mut work, failure), - failure - ); - assert_eq!(work.conversion_attempts, 1); - assert_eq!(work.conversion_failures, 1); - assert_eq!(work.atomic_discards, 1); - assert_eq!(work.conversion_completions, 0); - } - } - - #[test] - fn runtime_calls_round_trip_without_source_definition_edges() { - let candidate = payload(vec![instruction(DurableAirInstData::RuntimeCall { - runtime: rue_air::RuntimeCallKind::ToString, - args: Arc::from([]), - })]); - assert!(candidate.referenced_definition_keys().is_empty()); - - let mut work = DurableBodyWork::default(); - let projected = candidate.project_semantic_body(&mut work).unwrap(); - assert!(matches!( - projected.instructions[0].data, - rue_air::SemanticBodyInstData::RuntimeCall { - runtime: rue_air::RuntimeCallKind::ToString, - .. - } - )); - } - - #[test] - fn projection_rejects_non_permutation_source_order() { - let candidate = payload(vec![ - instruction(DurableAirInstData::UnitConst), - instruction(DurableAirInstData::StructInit { - struct_key: rue_air::NominalInstanceKey::Named(StableDefinitionKey::for_test( - ModuleId::from_logical_path("main.rue").unwrap(), - StableDefinitionNamespace::Type, - StableDefinitionKind::Struct, - "S", - None, - )), - fields: Arc::from([0]), - source_order: Arc::from([1]), - }), - ]); - let mut work = DurableBodyWork::default(); - assert_eq!( - candidate.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::InvalidSourceOrder) - ); - assert_eq!(work.projection_failures, 1); - } - - #[test] - fn builtin_nominals_reject_unknown_names_and_wrong_kinds() { - use rue_air::SemanticImportNominalKind::{Enum, Struct}; - assert_eq!( - canonical_builtin_nominal(&Arc::from("MissingBuiltin"), Struct), - Err(DurableBodyConversionFailure::WrongDefinitionKind) - ); - assert_eq!( - canonical_builtin_nominal(&Arc::from("StrBuf"), Struct), - Err(DurableBodyConversionFailure::WrongDefinitionKind) - ); - assert_eq!( - canonical_builtin_nominal(&Arc::from("str"), Struct), - Ok(SemanticImportType::BuiltinNominal { - name: Arc::from("str"), - kind: Struct, - }) - ); - assert_eq!( - canonical_builtin_nominal(&Arc::from("Arch"), Struct), - Err(DurableBodyConversionFailure::WrongDefinitionKind) - ); - assert_eq!( - canonical_builtin_nominal(&Arc::from("Arch"), Enum), - Ok(SemanticImportType::BuiltinNominal { - name: Arc::from("Arch"), - kind: Enum, - }) - ); - assert_eq!( - canonical_builtin_nominal(&Arc::from("str"), Enum), - Err(DurableBodyConversionFailure::WrongDefinitionKind) - ); } } diff --git a/crates/rue-compiler/src/durable_compatibility_tests.rs b/crates/rue-compiler/src/durable_compatibility_tests.rs deleted file mode 100644 index 631a35b8d..000000000 --- a/crates/rue-compiler/src/durable_compatibility_tests.rs +++ /dev/null @@ -1,1072 +0,0 @@ -//! Bounded compatibility matrix for every durable semantic schema family. -//! -//! The literal version tuple and exhaustive kind sets are deliberate review -//! gates: any schema bump or newly introduced canonical variant must update -//! this matrix in the same change. - -use std::{collections::BTreeSet, sync::Arc}; - -use rue_air::{ - AirArgMode, RuntimeCallKind, SEMANTIC_BODY_INST_KINDS, SEMANTIC_IMPORT_CONST_KINDS, - SEMANTIC_IMPORT_TYPE_KINDS, STABLE_DEFINITION_KINDS, STABLE_DEFINITION_NAMESPACES, - SemanticBodyCallArg, SemanticBodyInstData as D, SemanticBodyMatchArm, SemanticBodyPattern, - SemanticImportConstValue as C, SemanticImportNominalKind, SemanticImportType as T, - SemanticSpecializationIdentity, -}; -use rue_span::FileId; - -use crate::*; - -const REVIEWED_SEMANTIC_SCHEMA: DurableSemanticSchemaVersion = DurableSemanticSchemaVersion { - major: 1, - minor: 0, - // Epoch 6: slice identity includes the resolved element type. - implementation_epoch: 6, -}; -// 10/9: the body algebra gained the recorded method-reference payload -// (RUE-1128); the payload-free 9/8 shapes fail closed to ordinary analysis. -const REVIEWED_ORDINARY_BODY_SCHEMA: u32 = 10; -const REVIEWED_SPECIALIZED_BODY_SCHEMA: u32 = 9; -const REVIEWED_BODY_KINDS: usize = 58; -const REVIEWED_TYPE_KINDS: usize = 21; -const REVIEWED_CONST_KINDS: usize = 6; -const REVIEWED_DECLARATION_PAYLOAD_KINDS: usize = 6; -const REVIEWED_DEFINITION_KINDS: usize = 8; -const REVIEWED_DEFINITION_NAMESPACES: usize = 4; - -fn module(path: &str) -> ModuleId { - ModuleId::from_logical_path(path).unwrap() -} - -fn key( - kind: StableDefinitionKind, - namespace: StableDefinitionNamespace, - name: &str, -) -> StableDefinitionKey { - StableDefinitionKey::for_test(module("pkg/main.rue"), namespace, kind, name, None) -} - -fn owner() -> StableDefinitionKey { - key( - StableDefinitionKind::Function, - StableDefinitionNamespace::Value, - "main", - ) -} - -fn fingerprint(owner: StableDefinitionKey) -> StableDefinitionInputFingerprint { - StableDefinitionInputFingerprint { - schema_version: 1, - key: owner, - declaration: StableDefinitionFingerprint::for_test(1), - signature: StableDefinitionFingerprint::for_test(2), - body_or_initializer: Some(StableDefinitionFingerprint::for_test(3)), - precision: StableDefinitionFingerprintPrecision::SignatureAndBody, - } -} - -fn body_payload(data: D) -> DurableOrdinaryBodyPayload { - let owner = owner(); - DurableOrdinaryBodyPayload { - schema_version: DURABLE_ORDINARY_BODY_SCHEMA_VERSION, - semantic_schema_version: DURABLE_SEMANTIC_SCHEMA_VERSION, - owner: owner.clone(), - expected_inputs: fingerprint(owner), - body: rue_air::SemanticBody { - return_type: T::Unit, - instructions: Arc::from([rue_air::SemanticBodyInst { - data, - ty: T::Unit, - anchor: rue_air::SemanticBodyAnchor { start: 0, end: 0 }, - }]), - places: Arc::from([]), - strings: Arc::from([]), - local_atoms: Arc::from([]), - param_drops: Arc::from([]), - borrow_slots: Arc::from([]), - num_locals: 0, - num_param_slots: 0, - param_by_ref: Arc::from([]), - param_writable: Arc::from([]), - allow_unreachable_code: false, - warnings: Arc::from([]), - method_references: Arc::from([]), - }, - } -} - -#[test] -fn reviewed_schema_versions_and_old_new_policy_are_explicit() { - assert_eq!(DURABLE_SEMANTIC_SCHEMA_VERSION, REVIEWED_SEMANTIC_SCHEMA); - assert_eq!( - DURABLE_ORDINARY_BODY_SCHEMA_VERSION, - REVIEWED_ORDINARY_BODY_SCHEMA - ); - assert_eq!( - DURABLE_SPECIALIZED_BODY_SCHEMA_VERSION, - REVIEWED_SPECIALIZED_BODY_SCHEMA - ); - assert_eq!(SEMANTIC_BODY_INST_KINDS.len(), REVIEWED_BODY_KINDS); - assert_eq!(SEMANTIC_IMPORT_TYPE_KINDS.len(), REVIEWED_TYPE_KINDS); - assert_eq!(SEMANTIC_IMPORT_CONST_KINDS.len(), REVIEWED_CONST_KINDS); - assert_eq!(STABLE_DEFINITION_KINDS.len(), REVIEWED_DEFINITION_KINDS); - assert_eq!( - STABLE_DEFINITION_NAMESPACES.len(), - REVIEWED_DEFINITION_NAMESPACES - ); - - let current = DURABLE_SEMANTIC_SCHEMA_VERSION; - assert!(current.accepts(current)); - for candidate in [ - DurableSemanticSchemaVersion { - major: current.major.saturating_sub(1), - ..current - }, - DurableSemanticSchemaVersion { - major: current.major + 1, - ..current - }, - DurableSemanticSchemaVersion { - minor: current.minor + 1, - ..current - }, - DurableSemanticSchemaVersion { - implementation_epoch: current.implementation_epoch.saturating_sub(1), - ..current - }, - DurableSemanticSchemaVersion { - implementation_epoch: current.implementation_epoch + 1, - ..current - }, - ] { - assert!( - !current.accepts(candidate), - "accepted unknown schema {candidate:?}" - ); - } - - for version in [ - DURABLE_ORDINARY_BODY_SCHEMA_VERSION - 1, - DURABLE_ORDINARY_BODY_SCHEMA_VERSION + 1, - u32::MAX, - ] { - let mut payload = body_payload(D::UnitConst); - payload.schema_version = version; - let mut work = DurableBodyWork::default(); - assert_eq!( - payload.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::SchemaVersionMismatch) - ); - assert_eq!(work.projection_failures, 1); - assert_eq!(work.projection_completions, 0); - } -} - -#[test] -fn pre_method_reference_payload_bodies_fail_closed_to_ordinary_analysis() { - // A retained candidate from the payload-free shape (schema 9, before the - // recorded method-reference payload landed) must never project into the - // current shape with a silently empty recorded reference set; the stale - // schema is rejected wholesale so the body falls back to ordinary - // analysis. - let mut payload = body_payload(D::UnitConst); - payload.schema_version = 9; - let mut work = DurableBodyWork::default(); - assert_eq!( - payload.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::SchemaVersionMismatch) - ); - assert_eq!(work.projection_attempts, 1); - assert_eq!(work.projection_failures, 1); - assert_eq!(work.projection_completions, 0); -} - -#[test] -fn every_type_const_and_specialization_identity_round_trips_same_version() { - let structure = key( - StableDefinitionKind::Struct, - StableDefinitionNamespace::Type, - "Record", - ); - let function = key( - StableDefinitionKind::Function, - StableDefinitionNamespace::Value, - "helper", - ); - let dependency = module("pkg/dependency.rue"); - let types = vec![ - T::I8, - T::I16, - T::I32, - T::I64, - T::U8, - T::U16, - T::U32, - T::U64, - T::Bool, - T::Unit, - T::Never, - T::ComptimeType, - T::BuiltinNominal { - name: Arc::from("str"), - kind: SemanticImportNominalKind::Struct, - }, - T::Nominal(structure.clone()), - T::Array { - element: Box::new(T::I32), - len: 4, - }, - T::Slice { - element: Box::new(T::I32), - name: Arc::from("[i32]"), - }, - T::PtrConst(Box::new(T::I32)), - T::PtrMut(Box::new(T::I32)), - T::Module(dependency.clone()), - T::GenericParameter(0), - T::AnonymousNominal(rue_air::AnonymousNominalKey { - kind: rue_air::AnonymousNominalKind::Struct, - producer: rue_air::StableProducerId::Function(Box::new( - rue_air::FunctionInstanceKey::Specialization { - base: Box::new(rue_air::FunctionInstanceKey::Definition(function.clone())), - arguments: rue_air::CanonicalArguments { - types: Arc::from([rue_air::TypeInstanceKey::Nominal( - rue_air::NominalInstanceKey::Named(structure.clone()), - )]), - values: Arc::from([rue_air::CanonicalArgumentValue::Type(Box::new( - rue_air::TypeInstanceKey::Module(dependency.clone()), - ))]), - }, - }, - )), - anchor: rue_rir::RirStructuralAnchor::new(vec![ - rue_rir::RirStructuralPathSegment::Body, - rue_rir::RirStructuralPathSegment::AnonymousType(0), - ]), - arguments: rue_air::CanonicalArguments::default(), - }), - ]; - assert_eq!( - types.iter().map(T::kind).collect::>(), - SEMANTIC_IMPORT_TYPE_KINDS.iter().copied().collect() - ); - - for value in &types { - let relocated = value - .try_map_identities(&|key| Ok::<_, ()>(key.clone()), &|module| { - Ok::<_, ()>(Arc::::from(module.as_str())) - }) - .unwrap(); - let restored = relocated - .try_map_identities(&|key| Ok::<_, ()>(key.clone()), &|path| { - Ok::<_, ()>(ModuleId::from_logical_path(path).unwrap()) - }) - .unwrap(); - assert_eq!(&restored, value); - } - - let consts = vec![ - C::Integer(i128::MAX), - C::Bool(true), - C::Type(types[14].clone()), - C::Function(function.clone()), - C::Unit, - C::String(Arc::from("hello")), - ]; - assert_eq!( - consts.iter().map(C::kind).collect::>(), - SEMANTIC_IMPORT_CONST_KINDS.iter().copied().collect() - ); - let identity = SemanticSpecializationIdentity { - base: function, - type_arguments: types.into(), - value_arguments: consts.into(), - }; - let relocated = identity - .try_map_keys(&|key| Ok::<_, ()>(key.clone()), &|module| { - Ok::<_, ()>(Arc::::from(module.as_str())) - }) - .unwrap(); - let restored = relocated - .try_map_keys(&|key| Ok::<_, ()>(key.clone()), &|path| { - Ok::<_, ()>(ModuleId::from_logical_path(path).unwrap()) - }) - .unwrap(); - assert_eq!(restored, identity); -} - -#[test] -fn every_declaration_payload_and_parameter_mode_imports_in_one_bounded_epoch() { - let structure = key( - StableDefinitionKind::Struct, - StableDefinitionNamespace::Type, - "Record", - ); - let enumeration = key( - StableDefinitionKind::Enum, - StableDefinitionNamespace::Type, - "Choice", - ); - let function = key( - StableDefinitionKind::Function, - StableDefinitionNamespace::Value, - "compute", - ); - let constant = key( - StableDefinitionKind::ValueConst, - StableDefinitionNamespace::Value, - "ANSWER", - ); - let module_binding = key( - StableDefinitionKind::ModuleBinding, - StableDefinitionNamespace::Value, - "support", - ); - let destructor = key( - StableDefinitionKind::Destructor, - StableDefinitionNamespace::Destructor, - "Record", - ); - let method = StableDefinitionKey::for_test( - module("pkg/main.rue"), - StableDefinitionNamespace::Method, - StableDefinitionKind::Method, - "read", - Some((StableDefinitionKind::Struct, Arc::from("Record"))), - ); - let associated = StableDefinitionKey::for_test( - module("pkg/main.rue"), - StableDefinitionNamespace::Method, - StableDefinitionKind::AssociatedFunction, - "new", - Some((StableDefinitionKind::Struct, Arc::from("Record"))), - ); - let parameters = [ - (DurableParameterMode::Value, false), - (DurableParameterMode::Borrow, false), - (DurableParameterMode::Inout, true), - ] - .into_iter() - .map(|(mode, is_comptime)| DurableSemanticParameter { - name: Arc::from("param"), - ty: T::PtrConst(Box::new(T::Nominal(structure.clone()))), - mode, - is_comptime, - }) - .collect::>(); - let declarations = vec![ - DurableDeclarationSemantic { - key: function.clone(), - is_public: true, - payload: DurableDeclarationPayload::Callable { - parameters: parameters.into(), - result: T::Nominal(enumeration.clone()), - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }, - }, - DurableDeclarationSemantic { - key: structure.clone(), - is_public: true, - payload: DurableDeclarationPayload::Struct { - fields: Arc::from([(Arc::from("value"), T::I32)]), - is_copy: false, - is_linear: false, - }, - }, - DurableDeclarationSemantic { - key: enumeration, - is_public: false, - payload: DurableDeclarationPayload::Enum { - variants: Arc::from([(Arc::from("Some"), Arc::from([T::I32]))]), - }, - }, - DurableDeclarationSemantic { - key: constant, - is_public: false, - payload: DurableDeclarationPayload::Const { - ty: T::I32, - value: C::Integer(42), - }, - }, - DurableDeclarationSemantic { - key: module_binding, - is_public: false, - payload: DurableDeclarationPayload::ModuleBinding { - target: module("pkg/support.rue"), - }, - }, - DurableDeclarationSemantic { - key: destructor, - is_public: false, - payload: DurableDeclarationPayload::Destructor, - }, - DurableDeclarationSemantic { - key: method, - is_public: false, - payload: DurableDeclarationPayload::Callable { - parameters: Arc::from([]), - result: T::I32, - has_self: true, - self_mode: DurableParameterMode::Borrow, - is_unchecked: false, - }, - }, - DurableDeclarationSemantic { - key: associated, - is_public: false, - payload: DurableDeclarationPayload::Callable { - parameters: Arc::from([]), - result: T::Nominal(structure.clone()), - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }, - }, - ]; - let payload_kinds = declarations - .iter() - .map(|declaration| match declaration.payload { - DurableDeclarationPayload::Callable { .. } => "callable", - DurableDeclarationPayload::Struct { .. } => "struct", - DurableDeclarationPayload::Enum { .. } => "enum", - DurableDeclarationPayload::Const { .. } => "const", - DurableDeclarationPayload::ModuleBinding { .. } => "module_binding", - DurableDeclarationPayload::Destructor => "destructor", - }) - .collect::>(); - assert_eq!(payload_kinds.len(), REVIEWED_DECLARATION_PAYLOAD_KINDS); - let identity_kinds = [ - ( - StableDefinitionKind::Function, - StableDefinitionNamespace::Value, - ), - ( - StableDefinitionKind::Struct, - StableDefinitionNamespace::Type, - ), - (StableDefinitionKind::Enum, StableDefinitionNamespace::Type), - ( - StableDefinitionKind::ValueConst, - StableDefinitionNamespace::Value, - ), - ( - StableDefinitionKind::ModuleBinding, - StableDefinitionNamespace::Value, - ), - ( - StableDefinitionKind::Destructor, - StableDefinitionNamespace::Destructor, - ), - ( - StableDefinitionKind::Method, - StableDefinitionNamespace::Method, - ), - ( - StableDefinitionKind::AssociatedFunction, - StableDefinitionNamespace::Method, - ), - ]; - assert_eq!(identity_kinds.len(), REVIEWED_DEFINITION_KINDS); - assert_eq!( - identity_kinds - .iter() - .map(|(kind, _)| *kind) - .collect::>(), - STABLE_DEFINITION_KINDS.iter().copied().collect() - ); - assert_eq!( - identity_kinds - .iter() - .map(|(_, namespace)| *namespace) - .collect::>(), - STABLE_DEFINITION_NAMESPACES.iter().copied().collect() - ); - for (kind, namespace) in identity_kinds { - assert_eq!(kind.namespace(), namespace); - } - import_durable_declaration_semantics(&declarations).unwrap(); -} - -#[test] -fn module_binding_declaration_import_is_explicitly_unsupported_and_fails_closed() { - let module_binding = DurableDeclarationSemantic { - key: key( - StableDefinitionKind::ModuleBinding, - StableDefinitionNamespace::Value, - "dependency", - ), - is_public: true, - payload: DurableDeclarationPayload::Const { - ty: T::Module(module("pkg/dependency.rue")), - value: C::Unit, - }, - }; - assert!( - matches!( - import_durable_declaration_semantics(&[module_binding]), - Err(rue_air::SemanticImportFailure::DeclarationKindMismatch) - ), - "module bindings have stable dependency keys but no durable declaration payload; a const-shaped payload must fail closed" - ); -} - -#[test] -fn valid_source_order_permutations_remain_distinct_and_projectable() { - let structure = key( - StableDefinitionKind::Struct, - StableDefinitionNamespace::Type, - "Pair", - ); - let payload = |source_order: Arc<[u32]>| { - let mut payload = body_payload(D::UnitConst); - payload.body.instructions = Arc::from([ - rue_air::SemanticBodyInst { - data: D::UnitConst, - ty: T::Unit, - anchor: rue_air::SemanticBodyAnchor { start: 0, end: 0 }, - }, - rue_air::SemanticBodyInst { - data: D::UnitConst, - ty: T::Unit, - anchor: rue_air::SemanticBodyAnchor { start: 0, end: 0 }, - }, - rue_air::SemanticBodyInst { - data: D::StructInit { - struct_key: rue_air::NominalInstanceKey::Named(structure.clone()), - fields: Arc::from([0, 1]), - source_order, - }, - ty: T::Nominal(structure.clone()), - anchor: rue_air::SemanticBodyAnchor { start: 0, end: 0 }, - }, - ]); - payload - }; - let declaration_order = payload(Arc::from([0, 1])); - let source_order = payload(Arc::from([1, 0])); - for candidate in [&declaration_order, &source_order] { - let mut work = DurableBodyWork::default(); - assert!(candidate.project_semantic_body(&mut work).is_ok()); - assert_eq!(work.projection_completions, 1); - assert_eq!(work.projection_failures, 0); - } - assert_ne!(declaration_order.body, source_order.body); -} - -#[test] -fn every_body_instruction_variant_preserves_kind_and_identity_on_roundtrip() { - let function = key( - StableDefinitionKind::Function, - StableDefinitionNamespace::Value, - "helper", - ); - let structure = key( - StableDefinitionKind::Struct, - StableDefinitionNamespace::Type, - "Record", - ); - let enumeration = key( - StableDefinitionKind::Enum, - StableDefinitionNamespace::Type, - "Choice", - ); - let args: Arc<[SemanticBodyCallArg]> = Arc::from([SemanticBodyCallArg { - value: 0, - mode: AirArgMode::Borrow, - }]); - let identity = SemanticSpecializationIdentity { - base: function.clone(), - type_arguments: Arc::from([T::Module(module("pkg/dependency.rue"))]), - value_arguments: Arc::from([C::Function(function.clone())]), - }; - let mut values = vec![ - D::Const(1), - D::BoolConst(true), - D::StringConst(0), - D::UnitConst, - D::TypeConst(T::Nominal(structure.clone())), - D::Add(0, 0), - D::Sub(0, 0), - D::Mul(0, 0), - D::WrappingAdd(0, 0), - D::WrappingSub(0, 0), - D::WrappingMul(0, 0), - D::Div(0, 0), - D::Mod(0, 0), - D::Eq(0, 0), - D::Ne(0, 0), - D::Lt(0, 0), - D::Gt(0, 0), - D::Le(0, 0), - D::Ge(0, 0), - D::And(0, 0), - D::Or(0, 0), - D::BitAnd(0, 0), - D::BitOr(0, 0), - D::BitXor(0, 0), - D::Shl(0, 0), - D::Shr(0, 0), - D::Neg(0), - D::Not(0), - D::BitNot(0), - D::Branch { - cond: 0, - then_value: 0, - else_value: Some(0), - }, - D::Loop { cond: 0, body: 0 }, - D::InfiniteLoop { body: 0 }, - D::Match { - scrutinee: 0, - arms: Arc::from([SemanticBodyMatchArm { - pattern: SemanticBodyPattern::EnumVariant { - enum_key: rue_air::NominalInstanceKey::Named(enumeration.clone()), - variant_index: 0, - }, - body: 0, - }]), - }, - D::Break, - D::Continue, - D::Alloc { slot: 0, init: 0 }, - D::Load { slot: 0 }, - D::Store { slot: 0, value: 0 }, - D::ParamStore { - param_slot: 0, - value: 0, - }, - D::Ret(Some(0)), - D::Call { - function: rue_air::FunctionInstanceKey::Definition(function.clone()), - args: args.clone(), - }, - D::RuntimeCall { - runtime: RuntimeCallKind::ToString, - args: args.clone(), - }, - D::CallSpecialized { - identity, - args: args.clone(), - }, - D::CallGeneric, - D::Intrinsic { - runtime: None, - name: Arc::from("test"), - args: args.clone(), - }, - D::Param { index: 0 }, - D::Block { - statements: Arc::from([0]), - value: 0, - }, - D::StructInit { - struct_key: rue_air::NominalInstanceKey::Named(structure.clone()), - fields: Arc::from([0]), - source_order: Arc::from([0]), - }, - D::ArrayInit { - elements: Arc::from([0]), - }, - D::PlaceRead { place: 0 }, - D::PlaceWrite { place: 0, value: 0 }, - D::EnumVariant { - enum_key: rue_air::NominalInstanceKey::Named(enumeration.clone()), - variant_index: 0, - payload: Arc::from([0]), - }, - D::EnumPayloadGet { - base: 0, - enum_key: rue_air::NominalInstanceKey::Named(enumeration), - variant_index: 0, - field_index: 0, - }, - D::IntCast { - value: 0, - from_ty: T::I32, - }, - D::Drop { value: 0 }, - D::StorageLive { slot: 0 }, - D::StorageDead { slot: 0 }, - D::MarkMoved { - value: 0, - slot: 0, - is_param: false, - place: Some(0), - }, - ]; - values.sort_by_key(D::kind); - assert_eq!( - values.iter().map(D::kind).collect::>(), - SEMANTIC_BODY_INST_KINDS - ); - for value in values { - let kind = value.kind(); - let relocated = value - .try_map_keys(&|key| Ok::<_, ()>(key.clone()), &|module| { - Ok::<_, ()>(Arc::::from(module.as_str())) - }) - .unwrap(); - let restored = relocated - .try_map_keys(&|key| Ok::<_, ()>(key.clone()), &|path| { - Ok::<_, ()>(ModuleId::from_logical_path(path).unwrap()) - }) - .unwrap(); - assert_eq!(restored.kind(), kind); - assert_eq!(restored, value); - } -} - -#[test] -fn corrupt_truncated_unknown_and_unsupported_bodies_fail_closed_individually() { - let cases = [ - ( - D::Add(0, 0), - DurableBodyProjectionFailure::ForwardInstructionReference, - ), - ( - D::StringConst(0), - DurableBodyProjectionFailure::InvalidStringReference, - ), - ( - D::PlaceRead { place: 0 }, - DurableBodyProjectionFailure::InvalidPlaceReference, - ), - ( - D::CallGeneric, - DurableBodyProjectionFailure::InvalidInstructionReference, - ), - ( - D::StructInit { - struct_key: rue_air::NominalInstanceKey::Named(key( - StableDefinitionKind::Struct, - StableDefinitionNamespace::Type, - "S", - )), - fields: Arc::from([]), - source_order: Arc::from([0]), - }, - DurableBodyProjectionFailure::InvalidSourceOrder, - ), - ]; - for (data, failure) in cases { - let payload = body_payload(data); - let mut work = DurableBodyWork::default(); - assert_eq!(payload.project_semantic_body(&mut work), Err(failure)); - assert_eq!(work.projection_attempts, 1); - assert_eq!(work.projection_failures, 1); - assert_eq!(work.projection_completions, 0); - } - - let mut truncated = body_payload(D::UnitConst); - truncated.body.num_param_slots = 1; - let mut work = DurableBodyWork::default(); - assert_eq!( - truncated.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::InvalidParameterModes) - ); - - let mut corrupt_anchor = body_payload(D::UnitConst); - Arc::make_mut(&mut corrupt_anchor.body.instructions)[0].anchor = - rue_air::SemanticBodyAnchor { start: 2, end: 1 }; - let mut work = DurableBodyWork::default(); - assert_eq!( - corrupt_anchor.project_semantic_body(&mut work), - Err(DurableBodyProjectionFailure::InvalidAnchor) - ); -} - -fn snapshot(entries: &[(u32, &str, &str, &str)], root: u32) -> SourceSnapshot { - let physical = entries - .iter() - .map(|(id, path, _, _)| (FileId::new(*id), (*path).to_owned())) - .collect(); - let logical = entries - .iter() - .map(|(id, _, path, _)| (FileId::new(*id), (*path).to_owned())) - .collect(); - SourceSnapshot::new( - SourceMetadata::new(FileId::new(root), physical, logical).unwrap(), - entries - .iter() - .map(|(id, _, _, source)| (FileId::new(*id), Arc::new((*source).to_owned()))) - .collect(), - ) - .unwrap() -} - -#[test] -fn relocation_file_ids_and_input_order_preserve_same_version_body_values() { - let original = snapshot( - &[ - ( - 91, - "/old/main.rue", - "main.rue", - "fn left() -> i32 { 1 } fn right() -> i32 { 2 } fn main() -> i32 { left() + right() }", - ), - (92, "/old/dead.rue", "dead.rue", "fn dead() -> i32 { 0 }"), - ], - 91, - ); - let relocated = snapshot( - &[ - (4, "/new/dead.rue", "dead.rue", "fn dead() -> i32 { 0 }"), - ( - 7, - "/new/main.rue", - "main.rue", - "fn left() -> i32 { 1 } fn right() -> i32 { 2 } fn main() -> i32 { left() + right() }", - ), - ], - 7, - ); - let options = CompileOptions::default(); - let mut warm = CompilerSession::new(); - warm.update(&original).into_result().unwrap(); - warm.canonical_semantic(&options).unwrap(); - warm.update(&relocated).into_result().unwrap(); - let reused = warm.canonical_semantic(&options).unwrap(); - assert_eq!(reused.work().durable_bodies.reused_bodies, 0); - assert_eq!(reused.work().durable_bodies.candidate_fallbacks, 0); - assert_eq!(reused.work().body_analysis.bodies_attempted, 0); - assert_eq!(reused.work().body_analysis.ordinary_body_import_attempts, 3); - assert_eq!( - reused.work().body_analysis.ordinary_body_import_successes, - 3 - ); - - let mut cold = CompilerSession::new(); - cold.update(&relocated).into_result().unwrap(); - let fresh = cold.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", reused.functions()), - format!("{:?}", fresh.functions()) - ); - assert_eq!(reused.strings(), fresh.strings()); - assert_eq!(reused.type_pool().stats(), fresh.type_pool().stats()); -} - -/// Warm==cold parity for the recorded method-reference payload (RUE-1128): a -/// body analyzed fresh and the same body imported from a durable candidate -/// carry identical recorded `(receiver, method)` sets, covering a -/// builtin/lang-item bare-symbol owner (`StrBuf` string concatenation), a -/// direct user-struct method call, and a specialized-generic receiver. -#[test] -fn recorded_method_references_are_identical_fresh_and_imported() { - use std::collections::{BTreeMap, HashMap, HashSet}; - - let strbuf_source = r#" -pub struct StrBuf { - buf: ptr mut u8, - len: u64, - cap: u64, - - fn concat_borrowed(borrow first: Self, borrow second: Self) -> Self { - Self { buf: first.buf, len: first.len + second.len, cap: 0 } - } - - fn len(borrow self) -> u64 { self.len } - fn as_ptr(borrow self) -> ptr mut u8 { self.buf } -} - -drop fn StrBuf(self) { } -"#; - let root_source = r#" -const strbuf = @import("std/strbuf.rue"); -const helper = @import("helper.rue"); - -struct Counter { - value: i32, - fn get(borrow self) -> i32 { self.value } -} - -fn read(comptime T: type, value: T) -> i32 { value.get() } - -fn main() -> i32 { - println("n: " + @to_string(3)); - let counter = Counter { value: 2 }; - counter.get() + read(Counter, Counter { value: 1 }) + helper.bump() -} -"#; - let trusted = |helper_source: &str| { - let root = FileId::new(1); - let helper = FileId::new(2); - let strbuf = FileId::new(3); - let metadata = SourceMetadata::new_with_trusted_standard_library( - root, - HashMap::from([ - (root, "/project/main.rue".to_owned()), - (helper, "/project/helper.rue".to_owned()), - (strbuf, "/project/std/strbuf.rue".to_owned()), - ]), - HashMap::from([ - (root, "main.rue".to_owned()), - (helper, "helper.rue".to_owned()), - (strbuf, "\0rue-std/strbuf.rue".to_owned()), - ]), - HashSet::from([strbuf]), - ) - .unwrap(); - SourceSnapshot::new( - metadata, - vec![ - (root, Arc::new(root_source.to_owned())), - (helper, Arc::new(helper_source.to_owned())), - (strbuf, Arc::new(strbuf_source.to_owned())), - ], - ) - .unwrap() - }; - // Editing only the helper's body between revisions keeps every other - // body's durable candidate retained, so the warm run's composition - // reconstructs the interesting bodies from their durable candidates. - let original = trusted("pub fn bump() -> i32 { 1 }\n"); - let edited = trusted("pub fn bump() -> i32 { 2 }\n"); - let options = CompileOptions::default(); - - // The recorded sets ride the durable canonical bodies inside each retained - // body transaction — the exact candidates warm composition imports. - fn recorded( - session: &CompilerSession, - options: &CompileOptions, - ) -> BTreeMap> { - session - .retained_body_identity_states_for_test(options) - .into_iter() - .filter_map(|(identity, transaction)| { - let crate::BodyTransaction::Success { body, .. } = transaction? else { - return None; - }; - let body = match body.as_ref() { - crate::body_query::CanonicalBody::Ordinary { body, .. } - | crate::body_query::CanonicalBody::Anonymous { body, .. } - | crate::body_query::CanonicalBody::Specialization { body, .. } => body, - }; - Some(( - identity, - body.method_references - .iter() - .map(|reference| format!("{reference:?}")) - .collect(), - )) - }) - .collect() - } - - let mut warm = CompilerSession::new(); - crate::test_support::TestDiscoveryHost::new(&original) - .unwrap() - .drive(&mut warm) - .unwrap(); - warm.canonical_semantic(&options).unwrap(); - crate::test_support::TestDiscoveryHost::new(&edited) - .unwrap() - .drive(&mut warm) - .unwrap(); - let reused = warm.canonical_semantic(&options).unwrap(); - assert!( - reused.work().body_analysis.ordinary_body_import_successes > 0, - "the warm run must reconstruct bodies from durable candidates: {:?}", - reused.work().body_analysis - ); - assert!( - reused.work().body_analysis.bodies_attempted - < reused.work().body_analysis.ordinary_body_import_attempts, - "the warm-edit run retains the unedited bodies and re-imports them: {:?}", - reused.work().body_analysis - ); - - let mut cold = CompilerSession::new(); - crate::test_support::TestDiscoveryHost::new(&edited) - .unwrap() - .drive(&mut cold) - .unwrap(); - cold.canonical_semantic(&options).unwrap(); - - let warm_references = recorded(&warm, &options); - let fresh_references = recorded(&cold, &options); - assert_eq!( - warm_references, fresh_references, - "imported bodies must carry the recorded reference sets fresh analysis produced" - ); - - let (_, main_references) = fresh_references - .iter() - .find(|(owner, _)| owner.contains("kind: Function, name: \"main\"")) - .unwrap_or_else(|| panic!("main owns a durable body: {fresh_references:#?}")); - assert!( - main_references - .iter() - .any(|reference| reference.contains("StrBuf") && reference.contains("concat_borrowed")), - "the lang-item (bare-symbol) StrBuf concatenation is recorded: {main_references:?}" - ); - assert!( - main_references - .iter() - .any(|reference| reference.contains("Counter") && reference.contains("get")), - "the direct user-struct method call is recorded: {main_references:?}" - ); - let (_, specialized_references) = fresh_references - .iter() - .find(|(owner, _)| owner.starts_with("Specialization") && owner.contains("\"read\"")) - .unwrap_or_else(|| { - panic!("the read specialization owns a durable body: {fresh_references:#?}") - }); - assert!( - specialized_references - .iter() - .any(|reference| reference.contains("Counter") && reference.contains("get")), - "the specialized-generic receiver's method call is recorded: {specialized_references:?}" - ); -} - -#[test] -fn representative_body_families_project_and_import_through_production_reuse() { - let cases = [ - ( - "scalar-control-call", - "fn helper(x: i32) -> i32 { if x > 0 { x + 1 } else { x - 1 } }\n\ - fn main() -> i32 { helper(41) }", - ), - ( - "aggregate-place-enum-string", - r#" - struct Pair { x: i32, values: [i32; 2] } - enum Choice { Value(i32), Empty } - fn mutate(inout p: Pair) { p.x = 20; p.values[0] = 2; } - fn read(borrow p: Pair) -> i32 { p.x + p.values[0] } - fn main() -> i32 { - let mut p = Pair { x: 1, values: [3, 4] }; - mutate(inout p); - let choice = Choice.Value(read(borrow p)); - @dbg("compatibility"); - match choice { Choice.Value(v) => v, Choice.Empty => 0 } - } - "#, - ), - ]; - let options = CompileOptions::default(); - - for (name, source) in cases { - let original = snapshot(&[(71, "/old/main.rue", "main.rue", source)], 71); - let relocated = snapshot(&[(3, "/new/main.rue", "main.rue", source)], 3); - let mut warm = CompilerSession::new(); - warm.update(&original).into_result().unwrap(); - warm.canonical_semantic(&options).unwrap(); - warm.update(&relocated).into_result().unwrap(); - let reused = warm.canonical_semantic(&options).unwrap(); - - let mut cold = CompilerSession::new(); - cold.update(&relocated).into_result().unwrap(); - let fresh = cold.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", reused.functions()), - format!("{:?}", fresh.functions()), - "{name} production import diverged from fresh analysis" - ); - assert_eq!(reused.strings(), fresh.strings()); - assert_eq!(reused.type_pool().stats(), fresh.type_pool().stats()); - } -} diff --git a/crates/rue-compiler/src/durable_semantics.rs b/crates/rue-compiler/src/durable_semantics.rs index 74da7b1c8..4f9e1a4e0 100644 --- a/crates/rue-compiler/src/durable_semantics.rs +++ b/crates/rue-compiler/src/durable_semantics.rs @@ -6,245 +6,27 @@ use std::sync::Arc; -#[cfg(test)] -use rue_air::SemanticExportFailure; use rue_air::{ SemanticBinding, SemanticDeclarationExport, SemanticDeclarationPayload, SemanticDeclarationShell, SemanticExportConstValue, SemanticExportType, - SemanticImportConstValue, SemanticImportEpoch, SemanticImportFailure, SemanticImportNominal, - SemanticImportNominalKind, SemanticImportType, SemanticParameterMode, - StableDefinitionNamespace, + SemanticImportConstValue, SemanticImportType, SemanticParameterMode, StableDefinitionNamespace, }; +#[cfg(test)] +use rue_air::{SemanticExportFailure, SemanticImportNominalKind}; use rue_span::FileId; -/// Reconstruct the representable declaration universe in a new AIR epoch. -/// -/// Nominal shells are issued in stable-key order before any field or variant -/// type is imported, so mutually recursive pointer graphs are supported. The -/// returned epoch contains no handles from the exporting semantic request. -pub fn import_durable_declaration_semantics( - declarations: &[DurableDeclarationSemantic], -) -> Result>, SemanticImportFailure> { - fn payload_matches_key(declaration: &DurableDeclarationSemantic) -> bool { - matches!( - (declaration.key.kind(), &declaration.payload), - ( - StableDefinitionKind::Function - | StableDefinitionKind::Method - | StableDefinitionKind::AssociatedFunction, - DurableDeclarationPayload::Callable { .. } - ) | ( - StableDefinitionKind::Struct, - DurableDeclarationPayload::Struct { .. } - ) | ( - StableDefinitionKind::Enum, - DurableDeclarationPayload::Enum { .. } - ) | ( - StableDefinitionKind::ValueConst, - DurableDeclarationPayload::Const { .. } - ) | ( - StableDefinitionKind::Destructor, - DurableDeclarationPayload::Destructor - ) | ( - StableDefinitionKind::ModuleBinding, - DurableDeclarationPayload::ModuleBinding { .. } - ) - ) - } - - if declarations - .iter() - .any(|declaration| !payload_matches_key(declaration)) - { - return Err(SemanticImportFailure::DeclarationKindMismatch); - } - fn collect_modules(ty: &DurableType, modules: &mut std::collections::BTreeSet>) { - match ty { - DurableType::BuiltinNominal { .. } => {} - DurableType::Module(module) => { - modules.insert(Arc::from(module.as_str())); - } - DurableType::Array { element, .. } - | DurableType::Slice { element, .. } - | DurableType::PtrConst(element) - | DurableType::PtrMut(element) => collect_modules(element, modules), - _ => {} - } - } - let mut modules = std::collections::BTreeSet::>::new(); - let mut nominals = Vec::new(); - let mut functions = Vec::new(); - for declaration in declarations { - modules.insert(Arc::from(declaration.key.module().as_str())); - match &declaration.payload { - DurableDeclarationPayload::Callable { - parameters, result, .. - } => { - for parameter in parameters.iter() { - collect_modules(¶meter.ty, &mut modules); - } - collect_modules(result, &mut modules); - } - DurableDeclarationPayload::Struct { fields, .. } => { - for (_, ty) in fields.iter() { - collect_modules(ty, &mut modules); - } - } - DurableDeclarationPayload::Enum { variants } => { - for (_, payload) in variants.iter() { - for ty in payload.iter() { - collect_modules(ty, &mut modules); - } - } - } - DurableDeclarationPayload::Const { ty, value } => { - collect_modules(ty, &mut modules); - if let DurableConstValue::Type(ty) = value { - collect_modules(ty, &mut modules); - } - } - DurableDeclarationPayload::ModuleBinding { target } => { - modules.insert(Arc::from(target.as_str())); - } - DurableDeclarationPayload::Destructor => {} - } - match &declaration.payload { - DurableDeclarationPayload::Struct { .. } => nominals.push(SemanticImportNominal { - key: declaration.key.clone(), - module_path: Arc::from(declaration.key.module().as_str()), - name: Arc::from(declaration.key.name()), - kind: SemanticImportNominalKind::Struct, - is_public: declaration.is_public, - lang_item: if declaration.key.module().is_trusted_standard_library() { - rue_air::LangItem::from_standard_library_nominal( - declaration.key.module().as_str(), - declaration.key.name(), - ) - } else { - None - }, - }), - DurableDeclarationPayload::Enum { .. } => nominals.push(SemanticImportNominal { - key: declaration.key.clone(), - module_path: Arc::from(declaration.key.module().as_str()), - name: Arc::from(declaration.key.name()), - kind: SemanticImportNominalKind::Enum, - is_public: declaration.is_public, - lang_item: None, - }), - DurableDeclarationPayload::Callable { .. } => { - // Length-prefix every component. Unlike a source-like name this - // is injective for the full stable key, including its owner. - let owner = declaration.key.owner(); - let owner_module = owner.map(|owner| owner.module().as_str()).unwrap_or(""); - let owner_name = owner.map(|owner| owner.name()).unwrap_or(""); - let kind = format!("{:?}", declaration.key.kind()); - let identity = format!( - "{}:{}|{:?}|{}:{}|{}:{}|{}:{}|{:?}|{}:{}", - declaration.key.module().as_str().len(), - declaration.key.module().as_str(), - declaration.key.namespace(), - kind.len(), - kind, - declaration.key.name().len(), - declaration.key.name(), - owner_module.len(), - owner_module, - owner.map(|owner| owner.kind()), - owner_name.len(), - owner_name, - ); - functions.push((declaration.key.clone(), Arc::from(identity))); - } - _ => {} - } - } - let epoch = SemanticImportEpoch::new(nominals, functions, modules.into_iter().collect())?; - for declaration in declarations { - match &declaration.payload { - DurableDeclarationPayload::Struct { - fields, - is_copy, - is_linear, - } => { - let fields = fields - .iter() - .map(|(name, ty)| (name.clone(), ty.import_dto())) - .collect::>(); - epoch.complete_struct(&declaration.key, &fields, *is_copy, *is_linear)?; - } - DurableDeclarationPayload::Enum { variants } => { - let variants = variants - .iter() - .map(|(name, payload)| { - ( - name.clone(), - payload - .iter() - .map(DurableTypeProjection::import_dto) - .collect::>() - .into(), - ) - }) - .collect::>(); - epoch.complete_enum(&declaration.key, &variants)?; - } - DurableDeclarationPayload::Const { ty, value } => { - epoch.import_type(&ty.import_dto())?; - epoch.import_const_value(&value.import_dto())?; - } - DurableDeclarationPayload::Callable { - parameters, result, .. - } => { - let parameters = parameters - .iter() - .map(|parameter| (parameter.ty.import_dto(), parameter.is_comptime)) - .collect::>(); - epoch.validate_callable_signature(¶meters, &result.import_dto())?; - } - DurableDeclarationPayload::Destructor => {} - DurableDeclarationPayload::ModuleBinding { target } => { - epoch.import_type(&DurableType::Module(target.clone()).import_dto())?; - } - } - } - Ok(epoch) -} - use crate::{ BoundDefinitionSet, CanonicalMergedProgram, ModuleId, StableDefinitionKey, StableDefinitionKind, }; -/// Version of the canonical durable type/value algebra and its in-process -/// implementation namespace. Compatibility is explicit and fail closed. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DurableSemanticSchemaVersion { - pub major: u16, - pub minor: u16, - pub implementation_epoch: u32, -} - -pub const DURABLE_SEMANTIC_SCHEMA_VERSION: DurableSemanticSchemaVersion = - DurableSemanticSchemaVersion { - major: 1, - minor: 0, - // Epoch 6: slice identity carries its recursively resolved element - // type instead of collapsing to a synthetic builtin name. - implementation_epoch: 6, - }; - -const DURABLE_SEMANTIC_COMPATIBLE_MINORS: &[u16] = &[0]; +/// The durable specialization of rue-air's canonical type algebra. +pub type DurableType = SemanticImportType; -impl DurableSemanticSchemaVersion { - pub fn accepts(self, candidate: Self) -> bool { - self.major == candidate.major - && self.implementation_epoch == candidate.implementation_epoch - && candidate.minor <= self.minor - && DURABLE_SEMANTIC_COMPATIBLE_MINORS.contains(&candidate.minor) - } -} +/// The durable specialization of rue-air's canonical constant algebra. +pub type DurableConstValue = SemanticImportConstValue; -pub(crate) fn builtin_nominal_kind(name: &str) -> Option { +#[cfg(test)] +fn builtin_nominal_kind(name: &str) -> Option { if name == "str" { Some(SemanticImportNominalKind::Struct) } else if rue_builtins::get_builtin_enum(name).is_some() { @@ -254,12 +36,6 @@ pub(crate) fn builtin_nominal_kind(name: &str) -> Option; - -/// The durable specialization of rue-air's canonical constant algebra. -pub type DurableConstValue = SemanticImportConstValue; - /// Query-owned materialization payload for one anonymous nominal referenced by /// declaration semantics. Identity remains separate from shape so recursive /// uses and structurally equal aliases can be joined before fields are filled. @@ -313,36 +89,6 @@ pub enum DurableAnonymousMethodType { Concrete(DurableType), } -/// Relocation into the string-module body algebra is intentionally expressed -/// through the canonical rue-air traversal, not a compiler-owned enum match. -pub(crate) trait DurableTypeProjection { - fn import_dto(&self) -> SemanticImportType>; -} - -impl DurableTypeProjection for DurableType { - fn import_dto(&self) -> SemanticImportType> { - self.try_map_identities( - &|key| Ok::<_, std::convert::Infallible>(key.clone()), - &|module| Ok(Arc::from(module.as_str())), - ) - .expect("durable identity relocation is infallible") - } -} - -pub(crate) trait DurableConstValueProjection { - fn import_dto(&self) -> SemanticImportConstValue>; -} - -impl DurableConstValueProjection for DurableConstValue { - fn import_dto(&self) -> SemanticImportConstValue> { - self.try_map_identities( - &|key| Ok::<_, std::convert::Infallible>(key.clone()), - &|module| Ok(Arc::from(module.as_str())), - ) - .expect("durable identity relocation is infallible") - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DurableParameterMode { Value, @@ -1374,474 +1120,3 @@ impl From for DurableSemanticExportFailure { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::StableDefinitionNamespace; - use std::collections::HashSet; - use std::hash::{DefaultHasher, Hash, Hasher}; - - fn assert_query_value() {} - - #[test] - fn durable_values_have_query_key_traits() { - assert_query_value::(); - assert_query_value::(); - assert_query_value::(); - assert_query_value::(); - assert_query_value::(); - assert_query_value::(); - } - - #[test] - fn canonical_schema_policy_rejects_unknown_major_minor_and_implementation_epochs() { - let current = DURABLE_SEMANTIC_SCHEMA_VERSION; - assert!(current.accepts(current)); - assert!(!current.accepts(DurableSemanticSchemaVersion { - major: current.major + 1, - ..current - })); - assert!(!current.accepts(DurableSemanticSchemaVersion { - minor: current.minor + 1, - ..current - })); - assert!(!current.accepts(DurableSemanticSchemaVersion { - implementation_epoch: current.implementation_epoch + 1, - ..current - })); - } - - fn stable_hash(value: &T) -> u64 { - let mut hasher = DefaultHasher::new(); - value.hash(&mut hasher); - hasher.finish() - } - - #[test] - fn durable_types_are_canonical_aliases_without_peer_enum_definitions() { - let source = include_str!("durable_semantics.rs"); - assert!( - source.contains( - "pub type DurableType = SemanticImportType;" - ) - ); - assert!(source.contains( - "pub type DurableConstValue = SemanticImportConstValue;" - )); - let peer_type = ["pub enum Durable", "Type"].concat(); - let peer_value = ["pub enum Durable", "ConstValue"].concat(); - assert!(!source.contains(&peer_type)); - assert!(!source.contains(&peer_value)); - let ty: SemanticImportType = DurableType::I32; - let value: SemanticImportConstValue = - DurableConstValue::Type(ty); - assert_eq!( - value, - SemanticImportConstValue::Type(SemanticImportType::I32) - ); - } - - #[test] - fn bounded_durable_types_round_trip_across_fresh_epochs() { - let module = ModuleId::from_logical_path("pkg/main.rue").unwrap(); - let dependency = ModuleId::from_logical_path("pkg/dep.rue").unwrap(); - let structure = StableDefinitionKey::for_test( - module.clone(), - StableDefinitionNamespace::Type, - StableDefinitionKind::Struct, - "Record", - None, - ); - let enumeration = StableDefinitionKey::for_test( - module.clone(), - StableDefinitionNamespace::Type, - StableDefinitionKind::Enum, - "Choice", - None, - ); - let callable_key = StableDefinitionKey::for_test( - module, - StableDefinitionNamespace::Value, - StableDefinitionKind::Function, - "generic", - None, - ); - let declarations = vec![ - DurableDeclarationSemantic { - key: structure.clone(), - is_public: true, - payload: DurableDeclarationPayload::Struct { - fields: Arc::from([( - Arc::from("choice"), - DurableType::PtrConst(Box::new(DurableType::Nominal(enumeration.clone()))), - )]), - is_copy: false, - is_linear: false, - }, - }, - DurableDeclarationSemantic { - key: enumeration.clone(), - is_public: true, - payload: DurableDeclarationPayload::Enum { - variants: Arc::from([( - Arc::from("Only"), - Arc::from([ - DurableType::Array { - element: Box::new(DurableType::PtrMut(Box::new( - DurableType::Nominal(structure.clone()), - ))), - len: 2, - }, - DurableType::PtrConst(Box::new(DurableType::BuiltinNominal { - name: Arc::from("str"), - kind: SemanticImportNominalKind::Struct, - })), - ]), - )]), - }, - }, - DurableDeclarationSemantic { - key: callable_key, - is_public: true, - payload: DurableDeclarationPayload::Callable { - parameters: Arc::from([ - DurableSemanticParameter { - name: Arc::from("T"), - ty: DurableType::ComptimeType, - mode: DurableParameterMode::Value, - is_comptime: true, - }, - DurableSemanticParameter { - name: Arc::from("value"), - ty: DurableType::GenericParameter(0), - mode: DurableParameterMode::Value, - is_comptime: false, - }, - DurableSemanticParameter { - name: Arc::from("dep"), - ty: DurableType::Module(dependency.clone()), - mode: DurableParameterMode::Value, - is_comptime: false, - }, - ]), - result: DurableType::Unit, - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }, - }, - ]; - let first = import_durable_declaration_semantics(&declarations).unwrap(); - let second = import_durable_declaration_semantics(&declarations).unwrap(); - - let mut all = vec![ - DurableType::I8, - DurableType::I16, - DurableType::I32, - DurableType::I64, - DurableType::U8, - DurableType::U16, - DurableType::U32, - DurableType::U64, - DurableType::Bool, - DurableType::Unit, - DurableType::Never, - DurableType::ComptimeType, - DurableType::BuiltinNominal { - name: Arc::from("str"), - kind: SemanticImportNominalKind::Struct, - }, - DurableType::Nominal(structure), - DurableType::Nominal(enumeration), - DurableType::Module(dependency), - DurableType::Array { - element: Box::new(DurableType::U8), - len: u64::MAX, - }, - ]; - let mut frontier = all - .iter() - .filter(|ty| { - !matches!( - ty, - DurableType::ComptimeType - | DurableType::Module(_) - | DurableType::GenericParameter(_) - ) - }) - .cloned() - .collect::>(); - for depth in 1..=2 { - let mut next = Vec::with_capacity(frontier.len() * 3); - for ty in frontier { - next.push(DurableType::Array { - element: Box::new(ty.clone()), - len: depth, - }); - next.push(DurableType::PtrConst(Box::new(ty.clone()))); - next.push(DurableType::PtrMut(Box::new(ty))); - } - all.extend(next.iter().cloned()); - frontier = next; - } - - let mut unique = HashSet::new(); - for durable in all { - assert!(unique.insert(durable.clone())); - assert_eq!(stable_hash(&durable), stable_hash(&durable.clone())); - assert!(!format!("{durable:?}").is_empty()); - - let dto = durable.import_dto(); - let local = first.import_type(&dto).unwrap(); - let exported = first.export_type(local.clone()).unwrap(); - assert_eq!(exported, dto); - let remapped = second.import_type(&exported).unwrap(); - assert_eq!(second.export_type(remapped).unwrap(), dto); - assert_eq!( - second.export_type(local), - Err(SemanticImportFailure::ForeignLocalType) - ); - } - } - - #[test] - fn trusted_std_strbuf_survives_durable_import_with_aarch64_sret_identity() { - let key = StableDefinitionKey::for_test( - ModuleId::from_trusted_standard_library_path("\0rue-std/strbuf.rue").unwrap(), - StableDefinitionNamespace::Type, - StableDefinitionKind::Struct, - "StrBuf", - None, - ); - let epoch = import_durable_declaration_semantics(&[DurableDeclarationSemantic { - key, - is_public: true, - payload: DurableDeclarationPayload::Struct { - fields: Arc::from([ - ( - Arc::from("buf"), - DurableType::PtrMut(Box::new(DurableType::U8)), - ), - (Arc::from("len"), DurableType::U64), - (Arc::from("cap"), DurableType::U64), - ]), - is_copy: false, - is_linear: false, - }, - }]) - .unwrap(); - let strbuf = epoch - .type_pool() - .all_struct_ids() - .into_iter() - .find(|id| epoch.type_pool().struct_lang_item(*id) == Some(rue_air::LangItem::StrBuf)) - .expect("durable import should restore StrBuf language-item identity"); - assert_eq!(epoch.type_pool().struct_symbol_name(strbuf), "StrBuf"); - let frozen_types = epoch.type_pool().clone().freeze(); - assert!(rue_codegen::cfg_lower::type_uses_sret_return( - &frozen_types, - rue_air::Type::new_struct(strbuf), - 8, - )); - } - - #[test] - fn callable_parameter_order_is_semantic() { - let parameter = |ty| DurableSemanticParameter { - name: Arc::from("p"), - ty, - mode: DurableParameterMode::Value, - is_comptime: false, - }; - let a = DurableDeclarationPayload::Callable { - parameters: Arc::from([parameter(DurableType::Bool), parameter(DurableType::I32)]), - result: DurableType::Unit, - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }; - let b = DurableDeclarationPayload::Callable { - parameters: Arc::from([parameter(DurableType::I32), parameter(DurableType::Bool)]), - result: DurableType::Unit, - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }; - assert_ne!(a, b); - - let first = DurableConstValue::Type(DurableType::Array { - element: Box::new(DurableType::PtrConst(Box::new(DurableType::U8))), - len: 4, - }); - assert_eq!(first.clone(), first); - } - - fn test_key( - kind: StableDefinitionKind, - name: &str, - owner: Option<&str>, - ) -> StableDefinitionKey { - StableDefinitionKey::for_test( - ModuleId::from_logical_path("pkg/main.rue").unwrap(), - match kind { - StableDefinitionKind::Method | StableDefinitionKind::AssociatedFunction => { - StableDefinitionNamespace::Method - } - _ => StableDefinitionNamespace::Value, - }, - kind, - Arc::from(name), - owner.map(|name| (StableDefinitionKind::Struct, Arc::from(name))), - ) - } - - fn callable(key: StableDefinitionKey) -> DurableDeclarationSemantic { - DurableDeclarationSemantic { - key, - is_public: true, - payload: DurableDeclarationPayload::Callable { - parameters: Arc::from([]), - result: DurableType::Unit, - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }, - } - } - - #[test] - fn durable_callable_rejects_generic_indices_outside_its_type_parameters() { - let declaration = DurableDeclarationSemantic { - key: test_key(StableDefinitionKind::Function, "invalid", None), - is_public: true, - payload: DurableDeclarationPayload::Callable { - parameters: Arc::from([ - DurableSemanticParameter { - name: Arc::from("T"), - ty: DurableType::ComptimeType, - mode: DurableParameterMode::Value, - is_comptime: true, - }, - DurableSemanticParameter { - name: Arc::from("value"), - ty: DurableType::GenericParameter(1), - mode: DurableParameterMode::Value, - is_comptime: false, - }, - ]), - result: DurableType::GenericParameter(0), - has_self: false, - self_mode: DurableParameterMode::Value, - is_unchecked: false, - }, - }; - assert!(matches!( - import_durable_declaration_semantics(&[declaration]), - Err(SemanticImportFailure::GenericParameterOutOfRange) - )); - } - - #[test] - fn sibling_owned_callables_have_distinct_symbols_and_exact_round_trip() { - let left = test_key( - StableDefinitionKind::AssociatedFunction, - "make", - Some("Left"), - ); - let right = test_key( - StableDefinitionKind::AssociatedFunction, - "make", - Some("Right"), - ); - let epoch = import_durable_declaration_semantics(&[ - callable(left.clone()), - callable(right.clone()), - ]) - .unwrap(); - - let left_value = epoch - .import_const_value(&SemanticImportConstValue::Function(left.clone())) - .unwrap(); - let right_value = epoch - .import_const_value(&SemanticImportConstValue::Function(right.clone())) - .unwrap(); - assert_ne!(left_value, right_value); - assert_eq!( - epoch.export_const_value(left_value).unwrap(), - SemanticImportConstValue::Function(left) - ); - assert_eq!( - epoch.export_const_value(right_value).unwrap(), - SemanticImportConstValue::Function(right) - ); - } - - #[test] - fn callable_kind_is_part_of_the_local_identity() { - let method = StableDefinitionKey::for_test( - ModuleId::from_logical_path("pkg/main.rue").unwrap(), - StableDefinitionNamespace::Method, - StableDefinitionKind::Method, - "same", - Some((StableDefinitionKind::Struct, Arc::from("Owner"))), - ); - let associated = StableDefinitionKey::for_test( - ModuleId::from_logical_path("pkg/main.rue").unwrap(), - StableDefinitionNamespace::Method, - StableDefinitionKind::AssociatedFunction, - "same", - Some((StableDefinitionKind::Struct, Arc::from("Owner"))), - ); - let epoch = import_durable_declaration_semantics(&[ - callable(method.clone()), - callable(associated.clone()), - ]) - .unwrap(); - let method_value = epoch - .import_const_value(&SemanticImportConstValue::Function(method.clone())) - .unwrap(); - let associated_value = epoch - .import_const_value(&SemanticImportConstValue::Function(associated.clone())) - .unwrap(); - assert_ne!(method_value, associated_value); - assert_eq!( - epoch.export_const_value(method_value).unwrap(), - SemanticImportConstValue::Function(method) - ); - assert_eq!( - epoch.export_const_value(associated_value).unwrap(), - SemanticImportConstValue::Function(associated) - ); - } - - #[test] - fn import_rejects_payload_kind_mismatch_before_building_an_epoch() { - let declaration = DurableDeclarationSemantic { - key: test_key(StableDefinitionKind::Function, "wrong", None), - is_public: true, - payload: DurableDeclarationPayload::Struct { - fields: Arc::from([]), - is_copy: false, - is_linear: false, - }, - }; - assert!(matches!( - import_durable_declaration_semantics(&[declaration]), - Err(SemanticImportFailure::DeclarationKindMismatch) - )); - - let module_binding = DurableDeclarationSemantic { - key: test_key(StableDefinitionKind::ModuleBinding, "module", None), - is_public: true, - payload: DurableDeclarationPayload::Const { - ty: DurableType::Module(ModuleId::from_logical_path("pkg/dep.rue").unwrap()), - value: DurableConstValue::Unit, - }, - }; - assert!(matches!( - import_durable_declaration_semantics(&[module_binding]), - Err(SemanticImportFailure::DeclarationKindMismatch) - )); - } -} diff --git a/crates/rue-compiler/src/import_discovery.rs b/crates/rue-compiler/src/import_discovery.rs index c3a69a527..aedac011a 100644 --- a/crates/rue-compiler/src/import_discovery.rs +++ b/crates/rue-compiler/src/import_discovery.rs @@ -228,17 +228,10 @@ impl ImportDiscoveryRequest { } } -// RUE-1033 DELETION/REPLACEMENT GATE: retire the entire legacy supported -// host-driven import path together once a freshness- and speculation-safe -// replacement is stable. Its exact public bypass surface is inventoried by -// `legacy_import_authority_is_one_explicit_compatibility_boundary`. - /// Result of one host transaction. Denial and absence are intentionally /// distinct from an accepted source read. /// -/// Public construction participates in the legacy RUE-1033 compatibility -/// boundary. New consumers must use the unstable begin/frontier/publish -/// protocol and construct statuses only for requests emitted by its frontier. +/// Hosts construct statuses only for requests emitted by a compiler frontier. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ImportObservationStatus { Absent, @@ -318,8 +311,7 @@ impl ImportObservationStatus { /// Accepted source bytes paired with their transaction observation. /// -/// Public construction participates in the legacy RUE-1033 compatibility -/// boundary and is not independently freshness- or speculation-safe. +/// Source evidence for a compiler-emitted frontier request. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AcceptedImportSource { requested_path: Arc, @@ -331,11 +323,7 @@ pub struct AcceptedImportSource { } impl AcceptedImportSource { - /// Construct legacy host-observed source evidence. - /// - /// This constructor is part of the RUE-1033 compatibility boundary. New - /// consumers may use it only while answering a request emitted by the - /// unstable canonical frontier protocol. + /// Construct host-observed source evidence for a frontier request. pub fn new( requested_path: impl Into>, canonical_path: impl Into>, @@ -383,10 +371,7 @@ pub struct ImportObservation { } impl ImportObservation { - /// Construct a legacy absence observation. - /// - /// This is part of the RUE-1033 compatibility boundary and carries no - /// independent freshness authority. + /// Construct an absence observation for a frontier request. pub fn absent(request: ImportDiscoveryRequest) -> Self { Self { request, @@ -394,10 +379,7 @@ impl ImportObservation { accepted_source: None, } } - /// Construct a legacy accepted-source observation. - /// - /// This is part of the RUE-1033 compatibility boundary and carries no - /// independent freshness authority. + /// Construct an accepted-source observation for a frontier request. pub fn accepted( request: ImportDiscoveryRequest, source: AcceptedImportSource, @@ -418,10 +400,7 @@ impl ImportObservation { accepted_source: Some(source), }) } - /// Construct a legacy terminal-failure observation. - /// - /// This is part of the RUE-1033 compatibility boundary and carries no - /// independent freshness authority. + /// Construct a terminal-failure observation for a frontier request. pub fn failure( request: ImportDiscoveryRequest, status: ImportObservationStatus, @@ -468,10 +447,6 @@ impl ImportObservation { /// Deterministically ordered observations from one immutable epoch. /// -/// Public `Default` construction and mutation are retained only for the -/// RUE-1033 legacy compatibility boundary. A freely constructed ledger is not -/// freshness- or speculation-safe and must not seed a new canonical request. -/// /// The ledger is a persistent structure (RUE-1112): frozen segments are shared /// by `Arc` across clones, and entries recorded since the value was cloned live /// in a private head. Cloning therefore costs O(entries recorded since the @@ -502,11 +477,7 @@ struct ImportObservationSegment { const LEDGER_COMPACTION_DEPTH: usize = 16; impl ImportObservationLedger { - /// Record legacy host evidence. - /// - /// This mutator is part of the RUE-1033 compatibility boundary. New - /// consumers publish compiler-ordered batches through the unstable - /// canonical protocol instead. + /// Record one host observation while publishing a compiler-ordered batch. pub fn record(&mut self, observation: ImportObservation) -> CompileResult<()> { let request = observation.request.clone(); if let Some(previous) = self.get(&request) { @@ -923,11 +894,7 @@ impl ImportDiscoveryPlan { .map(|group| group[0].occurrence().clone()), ) } - /// Expose legacy host-driven candidate groups. - /// - /// This bypass is retained only for the RUE-1033 compatibility boundary. - /// It is not freshness- or speculation-safe; new consumers must request a - /// compiler-produced frontier through the unstable canonical protocol. + /// Ordered candidate groups retained by the compiler-owned plan. pub fn groups(&self) -> &[Arc<[ImportDiscoveryRequest]>] { self.groups.as_slice() } @@ -1528,7 +1495,7 @@ struct AssembledSource { pub struct AcceptedReadManifest { entries: crate::shared_segments::SharedSegments, /// Lazily materialized merged slice for consumers that need `Arc<[T]>` - /// (the RUE-1033 stable staging boundary and retained artifacts). + /// (the compiler-owned staging boundary and retained artifacts). merged: std::sync::OnceLock>, } @@ -1578,9 +1545,7 @@ impl AcceptedReadManifest { } } - /// Wrap a complete shared slice, sharing it without copying when it is - /// already in canonical module order (the compatibility boundary makes no - /// ordering promise, so an unordered slice is canonicalized by copy). + #[cfg(test)] pub(crate) fn from_shared(entries: Arc<[AcceptedReadManifestEntry]>) -> Self { if !entries.is_sorted_by(|a, b| accepted_read_order(a, b) != std::cmp::Ordering::Greater) { return Self::from_entries(entries.to_vec()); diff --git a/crates/rue-compiler/src/import_graph.rs b/crates/rue-compiler/src/import_graph.rs index 1a8a4a842..2a99bb285 100644 --- a/crates/rue-compiler/src/import_graph.rs +++ b/crates/rue-compiler/src/import_graph.rs @@ -421,6 +421,7 @@ impl ResolvedProgramRevision { } #[cfg(test)] + #[allow(dead_code)] pub(crate) fn imports(&self) -> &CanonicalImportGraph { &self.imports } @@ -439,11 +440,13 @@ impl ResolvedCodegenRevision { } #[cfg(test)] + #[allow(dead_code)] pub(crate) fn program(&self) -> &ResolvedProgramRevision { &self.program } #[cfg(test)] + #[allow(dead_code)] pub(crate) fn opt_level(&self) -> StableOptLevel { self.opt_level } diff --git a/crates/rue-compiler/src/lib.rs b/crates/rue-compiler/src/lib.rs index d50905519..ebf159e2e 100644 --- a/crates/rue-compiler/src/lib.rs +++ b/crates/rue-compiler/src/lib.rs @@ -57,7 +57,6 @@ mod linking; mod parsed_modules; mod program_image_plan; mod queries; -mod query_graph; mod revisioned_query_database; mod semantic_identity; mod semantic_query_nucleus; @@ -87,8 +86,6 @@ mod producer_nominal_acceptance_tests; #[cfg(test)] mod api_inventory; #[cfg(test)] -mod durable_compatibility_tests; -#[cfg(test)] mod integration_tests; #[cfg(test)] mod scaling_harness; @@ -174,16 +171,8 @@ pub(crate) use semantic_symbols::SemanticTranslationWork; #[allow(unused_imports)] pub(crate) use session::{ CompilerSessionWork, DefinitionQueryRecord, FRONTEND_DIAGNOSTIC_RETENTION_LIMIT, - FRONTEND_INVALIDATION_PLAN_RETENTION_LIMIT, FrontendQueryWork, FrontendRetentionMetrics, - ImportDiscoveryRevisionArtifact, ImportDiscoveryRevisionStatus, ImportGraphInputDescriptor, - SemanticDependencyBlocker, SemanticDependencyIncompleteReason, SemanticDependencyInputManifest, - SemanticDependencyManifestWork, SemanticDependencySurface, SemanticFullInvalidationReason, - SemanticInvalidationPlan, SemanticInvalidationScope, SemanticInvalidationWork, - SemanticQueryRecord, StableBodyDependencyInputRecord, StableBuiltinTypeCallHeadInput, - StableDeclarationTypeCallHeadDependency, StableDeclarationTypeDependency, - StableDefinitionFingerprint, StableDefinitionFingerprintPrecision, - StableDefinitionInputFingerprint, StableFreeFunctionDependency, StableModuleImportDependency, - StableNamedConstDependency, StableNamedConstDependencyTarget, + FrontendQueryWork, FrontendRetentionMetrics, ImportDiscoveryRevisionArtifact, + ImportDiscoveryRevisionStatus, ImportGraphInputDescriptor, SemanticQueryRecord, }; #[cfg(test)] pub(crate) use source_identity::LinkInputDescriptor; @@ -197,8 +186,7 @@ pub(crate) use source_identity::{ #[cfg(test)] pub(crate) use body_query::{BodyTransaction, transaction_equal}; pub(crate) use bound_definitions::{ - BoundDefinitionRecord, BoundDefinitionSet, StableDefinitionKey, StableDefinitionKind, - StableDefinitionNamespace, + BoundDefinitionSet, StableDefinitionKey, StableDefinitionKind, StableDefinitionNamespace, }; pub(crate) use canonical_lower::CanonicalRirOutput; pub(crate) use canonical_merge::CanonicalMergedProgram; @@ -207,25 +195,14 @@ pub(crate) use definition_snapshot::{ DefinitionKind, DefinitionNameKey, DefinitionNamespace, DefinitionOccurrenceId, DefinitionRecord, DefinitionSnapshot, }; -#[cfg(test)] -pub(crate) use durable_body::{ - DURABLE_ORDINARY_BODY_SCHEMA_VERSION, DURABLE_SPECIALIZED_BODY_SCHEMA_VERSION, - DurableBodyProjectionFailure, -}; -pub(crate) use durable_body::{ - DurableAirInstData, DurableOrdinaryBody, DurableOrdinaryBodyPayload, DurableProjection, - DurableSpecializedBodyPayload, convert_semantic_specialized_body_exports, -}; +pub(crate) use durable_body::{DurableAirInstData, DurableProjection}; #[cfg(test)] pub(crate) use durable_semantics::DurableDeclarationPayload; -pub(crate) use durable_semantics::{ - DURABLE_SEMANTIC_SCHEMA_VERSION, DurableDeclarationSemantic, DurableSemanticSchemaVersion, -}; +pub(crate) use durable_semantics::DurableDeclarationSemantic; pub(crate) use durable_semantics::{DurableConstValue, DurableType}; #[cfg(test)] pub(crate) use durable_semantics::{ - DurableParameterMode, DurableSemanticParameter, DurableSemanticProjectionFailure, - DurableSemanticProjectionWork, + DurableSemanticProjectionFailure, DurableSemanticProjectionWork, }; // Small foundational types callers need to configure or inspect the facade. @@ -243,10 +220,7 @@ pub(crate) use rue_span::Span; pub use rue_target::{Arch, Target}; // Internal phase vocabulary. These are intentionally not part of the facade. -pub(crate) use durable_body::{convert_semantic_body_exports, finalize_durable_ordinary_bodies}; -pub(crate) use durable_semantics::{ - import_durable_declaration_semantics, project_durable_declaration_semantics, -}; +pub(crate) use durable_semantics::project_durable_declaration_semantics; pub(crate) use import_graph::{validate_additive_successor, validate_canonical_import_graph}; #[cfg(test)] pub(crate) use linking::{parse_runtime_archive, validate_runtime}; diff --git a/crates/rue-compiler/src/pipeline_tests.rs b/crates/rue-compiler/src/pipeline_tests.rs index 65542763e..7c2089cf5 100644 --- a/crates/rue-compiler/src/pipeline_tests.rs +++ b/crates/rue-compiler/src/pipeline_tests.rs @@ -151,6 +151,117 @@ mod tests { assert_eq!(warm_execution.stderr, fresh_execution.stderr); } + /// ADR-0063 Phase 12's structural warm-edit gate. A body-only edit must + /// recompute exactly that frontend cone and replace only its `CodegenUnit`; + /// the deliberately fresh link still has to produce the same runnable + /// executable as a fresh session for the edited source. + #[cfg(unix)] + #[test] + fn warm_single_function_edit_recomputes_one_codegen_unit_then_fresh_links() { + let before = SourceSnapshot::single( + "", + "fn callee() -> i32 { 1 } fn main() -> i32 { callee() }", + ) + .unwrap(); + let after = SourceSnapshot::single( + "", + "fn callee() -> i32 { 2 } fn main() -> i32 { callee() }", + ) + .unwrap(); + let options = CompileOptions::default(); + + let mut warm_session = CompilerSession::new(); + warm_session + .update_for_presentation(&before) + .into_result() + .unwrap(); + crate::queries::compile_with_session(&mut warm_session, &before, &options).unwrap(); + warm_session + .update_for_presentation(&after) + .into_result() + .unwrap(); + let warm = + crate::queries::compile_with_session(&mut warm_session, &after, &options).unwrap(); + + let metrics = warm.unstable_metrics(); + assert_eq!(metrics.parsed.lexer_invocations, 1); + assert_eq!(metrics.parsed.parser_invocations, 1); + assert_eq!(metrics.parsed.modules_reparsed, 1); + assert_eq!( + metrics.lowered.parser_invocations, 0, + "the parsed successor reuses the canonical RIR lowering boundary" + ); + assert_eq!(metrics.semantic.body.analyses_computed, 1); + assert_eq!(metrics.semantic.body.analyses_reused, 1); + assert_eq!(metrics.semantic.cfg.cfg_builds_attempted, 1); + + assert_eq!(warm_session.codegen_executions().len(), 2); + assert!(warm_session.codegen_executions().iter().any(|(identity, execution)| { + matches!(identity, FunctionInstanceKey::Definition(definition) if definition.name() == "callee") + && *execution == rue_query::RequestExecution::Computed + })); + assert!(warm_session.codegen_executions().iter().any(|(identity, execution)| { + matches!(identity, FunctionInstanceKey::Definition(definition) if definition.name() == "main") + && *execution == rue_query::RequestExecution::Reused + })); + + let mut fresh_session = CompilerSession::new(); + fresh_session + .update_for_presentation(&after) + .into_result() + .unwrap(); + let fresh = + crate::queries::compile_with_session(&mut fresh_session, &after, &options).unwrap(); + assert_eq!(warm.elf, fresh.elf); + assert_eq!(warm.warnings, fresh.warnings); + + let execution = execute_compiled_output(&warm, "warm-single-function-edit"); + assert_eq!( + execution.status.code(), + Some(2), + "freshly linked warm output did not run: {execution:?}" + ); + assert!(execution.stdout.is_empty()); + assert!(execution.stderr.is_empty()); + } + + /// The shared query-worker budget may change scheduling only. The complete + /// fresh-link adapter must receive identical terminals and publish the + /// same executable bytes with either serial or parallel codegen work. + #[cfg(unix)] + #[test] + fn one_and_many_query_workers_produce_identical_linked_executables() { + let snapshot = SourceSnapshot::single( + "", + "fn a() -> i32 { 1 } fn b() -> i32 { 2 } fn c() -> i32 { 3 } fn main() -> i32 { a() + b() + c() }", + ) + .unwrap(); + let options = CompileOptions::default(); + let run = |workers| { + let mut session = CompilerSession::with_query_concurrency(workers); + session + .update_for_presentation(&snapshot) + .into_result() + .unwrap(); + crate::queries::compile_with_session(&mut session, &snapshot, &options).unwrap() + }; + + let one = run(1); + let many = run(4); + assert_eq!(one.elf, many.elf); + assert_eq!(one.warnings, many.warnings); + for (label, output) in [("one-worker", &one), ("many-worker", &many)] { + let execution = execute_compiled_output(output, label); + assert_eq!( + execution.status.code(), + Some(6), + "{label} linked output did not run: {execution:?}" + ); + assert!(execution.stdout.is_empty()); + assert!(execution.stderr.is_empty()); + } + } + #[cfg(unix)] #[test] fn named_const_strings_keep_stable_atoms_across_warm_reorder_and_codegen() { @@ -357,117 +468,6 @@ mod tests { assert_eq!(session.work().diagnostic_publications, 3); } - #[test] - fn successful_merge_reuse_keeps_canonical_origin_across_presentation_reordering() { - let first = FileId::new(1); - let second = FileId::new(2); - let metadata = SourceMetadata::new( - first, - HashMap::from([ - (first, "/project/first.rue".to_owned()), - (second, "/project/second.rue".to_owned()), - ]), - HashMap::from([ - (first, "first.rue".to_owned()), - (second, "second.rue".to_owned()), - ]), - ) - .unwrap(); - let first_text = Arc::new("fn main() -> i32 { 0 }".to_owned()); - let second_text = Arc::new("fn helper() {}".to_owned()); - let forward = SourceSnapshot::new( - metadata.clone(), - vec![(first, first_text.clone()), (second, second_text.clone())], - ) - .unwrap(); - let reverse = - SourceSnapshot::new(metadata, vec![(second, second_text), (first, first_text)]) - .unwrap(); - - let mut session = CompilerSession::new(); - session - .update_for_presentation(&forward) - .into_result() - .unwrap(); - session.merge().unwrap(); - let origin = session.latest_diagnostics().unwrap().clone(); - session - .update_for_presentation(&reverse) - .into_result() - .unwrap(); - let publications = session.work().diagnostic_publications; - let reuses = session.work().diagnostic_reuses; - - session.merge().unwrap(); - - assert!(Arc::ptr_eq(session.latest_diagnostics().unwrap(), &origin)); - assert_eq!(session.work().merge.executions, 1); - assert_eq!(session.work().merge.reuses, 1); - assert_eq!(session.work().diagnostic_publications, publications); - assert_eq!(session.work().diagnostic_reuses, reuses + 1); - } - - #[test] - fn failed_merge_attempt_is_recomputed_for_presentation_order() { - let first = FileId::new(1); - let second = FileId::new(2); - let metadata = SourceMetadata::new( - first, - HashMap::from([ - (first, "/project/first.rue".to_owned()), - (second, "/project/second.rue".to_owned()), - ]), - HashMap::from([ - (first, "first.rue".to_owned()), - (second, "second.rue".to_owned()), - ]), - ) - .unwrap(); - let first_text = Arc::new("fn duplicate() {} fn duplicate() {}".to_owned()); - let second_text = Arc::new("fn other() {} fn other() {}".to_owned()); - let forward = SourceSnapshot::new( - metadata.clone(), - vec![(first, first_text.clone()), (second, second_text.clone())], - ) - .unwrap(); - let reverse = - SourceSnapshot::new(metadata, vec![(second, second_text), (first, first_text)]) - .unwrap(); - - let mut session = CompilerSession::new(); - session - .update_for_presentation(&forward) - .into_result() - .unwrap(); - session.merge().unwrap_err(); - let forward_diagnostics = session.latest_diagnostics().unwrap().clone(); - assert_eq!( - forward_diagnostics - .errors() - .iter() - .map(|error| error.span().unwrap().file_id) - .collect::>(), - [first, second] - ); - - session - .update_for_presentation(&reverse) - .into_result() - .unwrap(); - session.merge().unwrap_err(); - let reverse_diagnostics = session.latest_diagnostics().unwrap(); - assert_eq!( - reverse_diagnostics - .errors() - .iter() - .map(|error| error.span().unwrap().file_id) - .collect::>(), - [second, first] - ); - assert!(!Arc::ptr_eq(&forward_diagnostics, reverse_diagnostics)); - assert_eq!(session.work().merge.executions, 2); - } - #[test] fn canonical_std_strbuf_identity_survives_qualified_and_aliased_lookup() { let context = ImportDiscoveryContext::new(1, "/project", Some("/sdk"), "test") @@ -706,20 +706,6 @@ mod tests { assert!(!semantic.type_pool().is_strbuf(spoof)); } - #[test] - fn canonical_single_source_adapter_executes_each_frontend_phase_once() { - let snapshot = SourceSnapshot::single("", "fn main() -> i32 { 42 }").unwrap(); - let (_rir, semantic, session) = - test_frontend_snapshot(&snapshot, &CompileOptions::default()).unwrap(); - assert_eq!(session.last_parse.syntax.parser_invocations, 1); - assert_eq!(session.rir.executions, 1); - assert_eq!(session.last_rir.modules_visited, 1); - assert_eq!(semantic.work().binding.bind_invocations, 1); - assert_eq!(semantic.work().cfg.cfg_builds_attempted, 1); - assert_eq!(semantic.work().cfg.cfg_builds_succeeded, 1); - assert_eq!(semantic.functions().len(), 1); - } - #[test] fn documented_snapshot_session_embedding_carries_complete_identity() { let root = FileId::new(7); diff --git a/crates/rue-compiler/src/queries.rs b/crates/rue-compiler/src/queries.rs index b785f33c2..b9901a2e7 100644 --- a/crates/rue-compiler/src/queries.rs +++ b/crates/rue-compiler/src/queries.rs @@ -164,9 +164,6 @@ pub(crate) struct CfgFrontendOutput { pub(crate) strings: Vec, /// Warnings collected during semantic analysis and CFG construction. pub(crate) warnings: Vec, - pub(crate) implicit_named_destructor_dependencies: - Vec, - pub(crate) implicit_named_destructor_dependencies_complete: bool, pub(crate) work: canonical_semantic::CfgConstructionWork, } @@ -672,8 +669,6 @@ pub(crate) fn collect_function_cfg_queries( .collect(); let mut functions = Vec::with_capacity(results.len()); - let mut implicit_named_destructor_dependencies = Vec::new(); - let mut implicit_named_destructor_dependencies_complete = true; let mut first_errors = None; for result in results { let (output, function_work) = match result { @@ -707,19 +702,14 @@ pub(crate) fn collect_function_cfg_queries( work.cfg_export_attempts += function_work.cfg_export_attempts; work.cfg_export_successes += function_work.cfg_export_successes; work.cfg_export_rejections += function_work.cfg_export_rejections; - if let Some((func, func_warnings, mut implicit_edges, complete)) = output { + if let Some((func, func_warnings, _, _)) = output { functions.push(func); warnings.extend(func_warnings); - implicit_named_destructor_dependencies.append(&mut implicit_edges); - implicit_named_destructor_dependencies_complete &= complete; } } if let Some(errors) = first_errors { return Err(CfgConstructionFailure { errors, work }); } - implicit_named_destructor_dependencies.sort(); - implicit_named_destructor_dependencies.dedup(); - info!( function_count = functions.len(), "CFG construction complete" @@ -730,8 +720,6 @@ pub(crate) fn collect_function_cfg_queries( type_pool, strings, warnings, - implicit_named_destructor_dependencies, - implicit_named_destructor_dependencies_complete, work, }) } @@ -869,11 +857,10 @@ pub(crate) fn pre_link_object_bytes_with_session( options: &CompileOptions, ) -> MultiErrorResult { let _span = info_span!("compile_pipeline_pre_link").entered(); - let rir = { - let _span = info_span!("semantic_astgen", phase = "program_construction").entered(); - session.canonical_rir()? - }; + let _rir_span = info_span!("semantic_astgen", phase = "program_construction").entered(); let semantic = session.canonical_semantic(options)?; + drop(_rir_span); + let rir = semantic.rir_owner(); let foreign_symbols = crate::backend::collect_foreign_symbols(rir.rir(), rir.semantic_symbols().interner()); let export_symbols = @@ -920,11 +907,10 @@ pub(crate) fn compile_with_session( let total_source_bytes: usize = snapshot.files().map(|source| source.source.len()).sum(); let _span = info_span!("compile_pipeline").entered(); - let rir = { - let _span = info_span!("semantic_astgen", phase = "program_construction").entered(); - session.canonical_rir()? - }; + let _rir_span = info_span!("semantic_astgen", phase = "program_construction").entered(); let semantic = session.canonical_semantic(options)?; + drop(_rir_span); + let rir = semantic.rir_owner(); let session_work = session.work().clone(); let foreign_symbols = crate::backend::collect_foreign_symbols(rir.rir(), rir.semantic_symbols().interner()); diff --git a/crates/rue-compiler/src/query_graph.rs b/crates/rue-compiler/src/query_graph.rs deleted file mode 100644 index e523b1397..000000000 --- a/crates/rue-compiler/src/query_graph.rs +++ /dev/null @@ -1,616 +0,0 @@ -//! Type-created dependency nodes shared by the typed family stores. -//! -//! The graph knows only opaque node identities and observed stamps. Family -//! keys and artifacts never enter this structure and no downcast is possible. - -use std::{ - any::TypeId, - collections::{BTreeMap, BTreeSet, VecDeque}, - marker::PhantomData, -}; - -use crate::typed_query_store::TerminalStamp; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct QueryNodeId(u64); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum DependencyStamp { - Leaf(u64), - Terminal(TerminalStamp), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct ObservedDependency { - pub(crate) node: QueryNodeId, - pub(crate) stamp: DependencyStamp, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum InvalidationCause { - StampChanged { - dependency: QueryNodeId, - previous: DependencyStamp, - current: DependencyStamp, - }, - StampDisappeared { - dependency: QueryNodeId, - previous: DependencyStamp, - }, -} - -#[derive(Debug, Clone)] -struct Node { - owner: TypeId, - stamp: Option, - direct: BTreeSet, - reverse: BTreeSet, - invalidated_by: BTreeSet, -} - -#[derive(Debug, Clone, Default)] -pub(crate) struct QueryGraph { - nodes: Vec, - free: Vec, - /// Disappeared nodes whose final store-owned tombstone was retired while - /// reverse dependency edges still need the old node identity. - /// - /// This is explicit graph ownership, not a second validation cache. Each - /// entry is pinned by at least one reverse edge and is reclaimed when the - /// last such edge is removed. - retained_disappeared: BTreeSet, - computing: Vec, - aborted_computations: BTreeSet, - invalidations: BTreeMap, -} - -impl QueryGraph { - pub(crate) fn allocate(&mut self) -> QueryNodeId { - if let Some(id) = self.free.pop() { - self.nodes[id.0 as usize] = Node { - owner: TypeId::of::(), - stamp: None, - direct: BTreeSet::new(), - reverse: BTreeSet::new(), - invalidated_by: BTreeSet::new(), - }; - return id; - } - let id = QueryNodeId(self.nodes.len() as u64); - self.nodes.push(Node { - owner: TypeId::of::(), - stamp: None, - direct: BTreeSet::new(), - reverse: BTreeSet::new(), - invalidated_by: BTreeSet::new(), - }); - id - } - - pub(crate) fn begin_computation(&mut self) { - self.aborted_computations.remove(&TypeId::of::()); - self.computing.push(TypeId::of::()); - } - - pub(crate) fn finish_computation(&mut self) { - let owner = self - .computing - .pop() - .expect("finishing a query computation requires an active query"); - assert_eq!(owner, TypeId::of::()); - } - - pub(crate) fn is_current_computation(&self) -> bool { - self.computing.last() == Some(&TypeId::of::()) - } - - pub(crate) fn abort_cycle(&mut self) { - let owner = TypeId::of::(); - let start = self - .computing - .iter() - .position(|active| *active == owner) - .expect("a cycle re-enters an active query family"); - for aborted in self.computing.drain(start..) { - self.aborted_computations.insert(aborted); - } - } - - pub(crate) fn take_aborted(&mut self) -> bool { - self.aborted_computations.remove(&TypeId::of::()) - } - - pub(crate) fn invalidation_count(&self) -> usize { - self.invalidations - .get(&TypeId::of::()) - .copied() - .unwrap_or(0) - } - - pub(crate) fn publish( - &mut self, - node: QueryNodeId, - stamp: DependencyStamp, - direct: impl IntoIterator, - ) { - let index = node.0 as usize; - assert_eq!(self.nodes[index].owner, TypeId::of::()); - let old_direct = std::mem::take(&mut self.nodes[index].direct); - let direct = direct.into_iter().collect::>(); - for dependency in old_direct { - let dependency_index = dependency.node.0 as usize; - self.nodes[dependency_index].reverse.remove(&node); - let remains_observed = direct.iter().any(|current| current.node == dependency.node); - if !remains_observed - && self.nodes[dependency_index].stamp.is_none() - && self.nodes[dependency_index].reverse.is_empty() - && self.retained_disappeared.contains(&dependency.node) - { - self.retire_untyped(dependency.node); - } - } - for dependency in &direct { - self.nodes[dependency.node.0 as usize].reverse.insert(node); - } - self.nodes[index].direct = direct; - let previous = self.nodes[index].stamp.replace(stamp); - self.nodes[index].invalidated_by.clear(); - if let Some(previous) = previous - && previous != stamp - { - self.propagate(node, previous, Some(stamp)); - } - self.restore_reverse(node); - } - - pub(crate) fn disappear(&mut self, node: QueryNodeId) { - let index = node.0 as usize; - assert_eq!(self.nodes[index].owner, TypeId::of::()); - if let Some(previous) = self.nodes[index].stamp.take() { - self.propagate(node, previous, None); - } - } - - /// Permanently retire an unreferenced retained node and recycle its slot. - /// Propagation happens before edges are detached so every dependent keeps - /// the precise disappeared-stamp cause. - pub(crate) fn retire(&mut self, node: QueryNodeId) { - let index = node.0 as usize; - assert_eq!(self.nodes[index].owner, TypeId::of::()); - self.disappear::(node); - // A disappeared stamp remains a bounded validation tombstone while a - // retained dependent still observes it. Reusing its slot would make - // that old typed edge appear valid for an unrelated publication. - if !self.nodes[index].reverse.is_empty() { - self.retained_disappeared.insert(node); - return; - } - self.retire_untyped(node); - } - - fn retire_untyped(&mut self, node: QueryNodeId) { - let index = node.0 as usize; - debug_assert!(self.nodes[index].stamp.is_none()); - debug_assert!(self.nodes[index].reverse.is_empty()); - self.retained_disappeared.remove(&node); - let direct = std::mem::take(&mut self.nodes[index].direct); - for dependency in direct { - let dependency_index = dependency.node.0 as usize; - self.nodes[dependency_index].reverse.remove(&node); - // A disappeared dependency may have been pinned solely by this - // tombstone. Reclaim it transitively as soon as its final reverse - // edge goes away, so churn cannot strand unbounded graph nodes. - if self.nodes[dependency_index].stamp.is_none() - && self.nodes[dependency_index].reverse.is_empty() - && self.retained_disappeared.contains(&dependency.node) - { - self.retire_untyped(dependency.node); - } - } - self.nodes[index].invalidated_by.clear(); - self.free.push(node); - } - - /// Nodes explicitly retained by the graph after their owning store or - /// leaf cache retired them. Every such node has disappeared and remains - /// pinned solely to preserve identities observed by reverse dependents. - pub(crate) fn retained_disappeared_count(&self) -> usize { - debug_assert!(self.retained_disappeared.iter().all(|node| { - let entry = &self.nodes[node.0 as usize]; - entry.stamp.is_none() && !entry.reverse.is_empty() - })); - self.retained_disappeared.len() - } - - pub(crate) fn observe(&self, node: QueryNodeId) -> Option { - let entry = &self.nodes[node.0 as usize]; - assert_eq!(entry.owner, TypeId::of::()); - entry.stamp.map(|stamp| ObservedDependency { node, stamp }) - } - - pub(crate) fn is_current(&self, node: QueryNodeId) -> bool { - let entry = &self.nodes[node.0 as usize]; - assert_eq!(entry.owner, TypeId::of::()); - entry.stamp.is_some() && entry.invalidated_by.is_empty() - } - - pub(crate) fn valid(&mut self, node: QueryNodeId) -> bool { - assert_eq!(self.nodes[node.0 as usize].owner, TypeId::of::()); - self.validate(node, &mut BTreeSet::new()) - } - - pub(crate) fn direct_dependencies( - &self, - node: QueryNodeId, - ) -> Vec { - let entry = &self.nodes[node.0 as usize]; - assert_eq!(entry.owner, TypeId::of::()); - entry.direct.iter().copied().collect() - } - - pub(crate) fn reverse_dependency_count(&self, node: QueryNodeId) -> usize { - let entry = &self.nodes[node.0 as usize]; - assert_eq!(entry.owner, TypeId::of::()); - entry.reverse.len() - } - - pub(crate) fn invalidation_causes( - &self, - node: QueryNodeId, - ) -> &BTreeSet { - assert_eq!(self.nodes[node.0 as usize].owner, TypeId::of::()); - &self.nodes[node.0 as usize].invalidated_by - } - - fn validate(&mut self, node: QueryNodeId, active: &mut BTreeSet) -> bool { - if !active.insert(node) { - return false; - } - let direct = self.nodes[node.0 as usize].direct.clone(); - let mut causes = BTreeSet::new(); - for observed in direct { - let current = self.nodes[observed.node.0 as usize].stamp; - match current { - Some(current) - if current == observed.stamp && self.validate(observed.node, active) => {} - Some(current) => { - causes.insert(InvalidationCause::StampChanged { - dependency: observed.node, - previous: observed.stamp, - current, - }); - } - None => { - causes.insert(InvalidationCause::StampDisappeared { - dependency: observed.node, - previous: observed.stamp, - }); - } - } - } - active.remove(&node); - self.nodes[node.0 as usize].invalidated_by = causes; - self.nodes[node.0 as usize].stamp.is_some() - && self.nodes[node.0 as usize].invalidated_by.is_empty() - } - - fn propagate( - &mut self, - dependency: QueryNodeId, - previous: DependencyStamp, - current: Option, - ) { - let mut queue = VecDeque::from([(dependency, previous, current)]); - while let Some((dependency, previous, current)) = queue.pop_front() { - let reverse = self.nodes[dependency.0 as usize].reverse.clone(); - for dependent in reverse { - let cause = current.map_or( - InvalidationCause::StampDisappeared { - dependency, - previous, - }, - |current| InvalidationCause::StampChanged { - dependency, - previous, - current, - }, - ); - let entry = &mut self.nodes[dependent.0 as usize]; - let was_valid = entry.invalidated_by.is_empty(); - if entry.invalidated_by.insert(cause) { - if was_valid { - *self.invalidations.entry(entry.owner).or_default() += 1; - } - if let Some(stamp) = entry.stamp { - queue.push_back((dependent, stamp, None)); - } - } - } - } - } - - fn restore_reverse(&mut self, dependency: QueryNodeId) { - let reverse = self.nodes[dependency.0 as usize].reverse.clone(); - for dependent in reverse { - if self.validate(dependent, &mut BTreeSet::new()) { - self.restore_reverse(dependent); - } - } - } -} - -#[derive(Debug)] -pub(crate) struct TypedNode { - id: QueryNodeId, - marker: PhantomData Q>, -} - -impl Copy for TypedNode {} - -impl Clone for TypedNode { - fn clone(&self) -> Self { - *self - } -} - -#[derive(Debug, Clone)] -struct RetainedLeaf { - value: I, - node: TypedNode, - stamp: u64, -} - -/// Bounded typed leaf publications. Equal values reselect their existing -/// publication; eviction makes the old stamp disappear before removal. -#[derive(Debug, Clone)] -pub(crate) struct TypedLeafStore { - retained: VecDeque>, - next_publication: u64, - limit: usize, - selected: Option, -} - -impl TypedLeafStore -where - I: Clone + Eq + 'static, -{ - pub(crate) fn new(limit: usize) -> Self { - Self { - retained: VecDeque::new(), - next_publication: 0, - limit, - selected: None, - } - } - - pub(crate) fn publish(&mut self, graph: &mut QueryGraph, value: I) -> ObservedDependency { - if let Some(index) = self.retained.iter().position(|entry| entry.value == value) { - let retained = self.retained.remove(index).unwrap(); - if self.selected != Some(retained.node.id()) { - if let Some(selected) = self.selected { - graph.disappear::(selected); - } - graph.publish::( - retained.node.id(), - DependencyStamp::Leaf(retained.stamp), - [], - ); - self.selected = Some(retained.node.id()); - } - let observed = graph.observe::(retained.node.id()).unwrap(); - self.retained.push_back(retained); - return observed; - } - let node = TypedNode::::allocate(graph); - let stamp = self.next_publication; - self.next_publication = self.next_publication.wrapping_add(1); - if let Some(selected) = self.selected { - graph.disappear::(selected); - } - graph.publish::(node.id(), DependencyStamp::Leaf(stamp), []); - self.selected = Some(node.id()); - let node_id = node.id(); - self.retained.push_back(RetainedLeaf { value, node, stamp }); - while self.retained.len() > self.limit { - let evicted = self.retained.pop_front().unwrap(); - graph.retire::(evicted.node.id()); - } - ObservedDependency { - node: node_id, - stamp: DependencyStamp::Leaf(stamp), - } - } - - /// Publish a request-value leaf without deselecting other retained values. - /// Complete option values coexist, allowing an older query variant to be - /// reused when a caller switches back to it. - pub(crate) fn publish_retained( - &mut self, - graph: &mut QueryGraph, - value: I, - ) -> ObservedDependency { - if let Some(index) = self.retained.iter().position(|entry| entry.value == value) { - let retained = self.retained.remove(index).unwrap(); - let observed = graph.observe::(retained.node.id()).unwrap_or_else(|| { - graph.publish::( - retained.node.id(), - DependencyStamp::Leaf(retained.stamp), - [], - ); - graph.observe::(retained.node.id()).unwrap() - }); - self.selected = Some(retained.node.id()); - self.retained.push_back(retained); - return observed; - } - let node = TypedNode::::allocate(graph); - let stamp = self.next_publication; - self.next_publication = self.next_publication.wrapping_add(1); - graph.publish::(node.id(), DependencyStamp::Leaf(stamp), []); - self.selected = Some(node.id()); - let node_id = node.id(); - self.retained.push_back(RetainedLeaf { value, node, stamp }); - while self.retained.len() > self.limit { - let evicted = self.retained.pop_front().unwrap(); - graph.retire::(evicted.node.id()); - } - ObservedDependency { - node: node_id, - stamp: DependencyStamp::Leaf(stamp), - } - } - - pub(crate) fn selected(&self, graph: &QueryGraph) -> Option { - self.selected.and_then(|node| graph.observe::(node)) - } -} - -impl TypedNode { - pub(crate) fn allocate(graph: &mut QueryGraph) -> Self { - Self { - id: graph.allocate::(), - marker: PhantomData, - } - } -} - -impl TypedNode { - pub(crate) fn id(&self) -> QueryNodeId { - self.id - } -} - -#[cfg(test)] -mod tests { - use super::*; - - enum SourceLeaf {} - enum Dependent {} - - #[test] - fn reverse_edges_dirty_restore_and_disappear_without_scanning() { - let mut graph = QueryGraph::default(); - let mut leaves = TypedLeafStore::new(2); - let one = leaves.publish(&mut graph, 1_u32); - let dependent = TypedNode::::allocate(&mut graph); - let terminal = TerminalStamp { - outcome: crate::typed_query_store::OutcomePublication::Success(1), - diagnostics: 2, - }; - graph.publish::(dependent.id(), DependencyStamp::Terminal(terminal), [one]); - assert!(graph.valid::(dependent.id())); - - leaves.publish(&mut graph, 2_u32); - assert!(!graph.valid::(dependent.id())); - let one_again = leaves.publish(&mut graph, 1_u32); - assert_eq!(one, one_again); - assert!(graph.valid::(dependent.id())); - - leaves.publish(&mut graph, 3_u32); - leaves.publish(&mut graph, 4_u32); - assert!(!graph.valid::(dependent.id())); - let _ = std::any::TypeId::of::(); - } - - #[test] - fn retiring_last_dependent_reclaims_pinned_disappeared_nodes_transitively() { - let mut graph = QueryGraph::default(); - let mut leaves = TypedLeafStore::new(1); - let leaf = leaves.publish(&mut graph, 1_u32); - let dependent = TypedNode::::allocate(&mut graph); - let terminal = TerminalStamp { - outcome: crate::typed_query_store::OutcomePublication::Success(1), - diagnostics: 2, - }; - graph.publish::(dependent.id(), DependencyStamp::Terminal(terminal), [leaf]); - - leaves.publish(&mut graph, 2_u32); - assert_eq!(graph.free.len(), 0, "the dependent pins the old leaf"); - graph.retire::(dependent.id()); - assert_eq!(graph.free.len(), 2); - - let node_count = graph.nodes.len(); - let _first = TypedNode::::allocate(&mut graph); - let _second = TypedNode::::allocate(&mut graph); - assert_eq!(graph.nodes.len(), node_count, "retired slots are recycled"); - } - - #[test] - fn replacing_edges_reclaims_a_disappeared_dependency_after_its_last_pin() { - let mut graph = QueryGraph::default(); - let mut leaves = TypedLeafStore::new(1); - let leaf = leaves.publish(&mut graph, 1_u32); - let dependent = TypedNode::::allocate(&mut graph); - let terminal = TerminalStamp { - outcome: crate::typed_query_store::OutcomePublication::Success(1), - diagnostics: 2, - }; - graph.publish::(dependent.id(), DependencyStamp::Terminal(terminal), [leaf]); - leaves.publish(&mut graph, 2_u32); - assert!(graph.free.is_empty()); - - graph.publish::(dependent.id(), DependencyStamp::Terminal(terminal), []); - assert_eq!(graph.free.len(), 1); - } - - #[test] - fn removing_a_reverse_edge_does_not_retire_a_store_owned_disappeared_leaf() { - let mut graph = QueryGraph::default(); - let mut leaves = TypedLeafStore::new(2); - let first = leaves.publish(&mut graph, 1_u32); - let dependent = TypedNode::::allocate(&mut graph); - let terminal = TerminalStamp { - outcome: crate::typed_query_store::OutcomePublication::Success(1), - diagnostics: 2, - }; - graph.publish::(dependent.id(), DependencyStamp::Terminal(terminal), [first]); - - leaves.publish(&mut graph, 2_u32); - graph.publish::(dependent.id(), DependencyStamp::Terminal(terminal), []); - assert!(graph.free.is_empty()); - assert_eq!(graph.retained_disappeared_count(), 0); - - assert_eq!(leaves.publish(&mut graph, 1_u32), first); - } - - #[test] - fn graph_explicitly_owns_disappeared_nodes_until_all_reverse_pins_retire() { - let mut graph = QueryGraph::default(); - let count = crate::typed_query_store::QUERY_TERMINAL_RETENTION_LIMIT * 3; - let terminal = TerminalStamp { - outcome: crate::typed_query_store::OutcomePublication::Success(1), - diagnostics: 2, - }; - let mut dependents = Vec::new(); - - for publication in 0..count { - let source = TypedNode::::allocate(&mut graph); - graph.publish::(source.id(), DependencyStamp::Leaf(publication as u64), []); - let observed = graph.observe::(source.id()).unwrap(); - let dependent = TypedNode::::allocate(&mut graph); - graph.publish::( - dependent.id(), - DependencyStamp::Terminal(terminal), - [observed], - ); - - graph.retire::(source.id()); - dependents.push(dependent); - } - - assert_eq!(graph.retained_disappeared_count(), count); - assert!(graph.free.is_empty()); - - for dependent in dependents.drain(..count / 2) { - graph.retire::(dependent.id()); - } - assert_eq!(graph.retained_disappeared_count(), count - count / 2); - assert_eq!(graph.free.len(), count); - - for dependent in dependents { - graph.retire::(dependent.id()); - } - assert_eq!(graph.retained_disappeared_count(), 0); - assert_eq!(graph.free.len(), count * 2); - } -} diff --git a/crates/rue-compiler/src/revisioned_query_database.rs b/crates/rue-compiler/src/revisioned_query_database.rs index cb50235d5..bbc9bcc27 100644 --- a/crates/rue-compiler/src/revisioned_query_database.rs +++ b/crates/rue-compiler/src/revisioned_query_database.rs @@ -4,8 +4,8 @@ //! record shape while moving key identity, execution, immutable attempts, //! dependency recording, and current/last-good publication into `rue-query`. //! It is a migration boundary, not a peer database. RUE-1033 / ADR-0063 Phase -//! 12 deletes this selected-state-shaped shim after every family calls the -//! runtime directly. +//! 12 registers each family directly with the runtime; native selection roots +//! retain the current and last-good terminals. use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; use std::rc::Rc; @@ -120,281 +120,6 @@ fn record_equal(left: &F::Record, right: &F::Record) -> boo && F::diagnostics_equal(left, right) } -pub(crate) struct RevisionedFamily -where - F: TypedQueryFamily + 'static, - F::Key: 'static, - F::Record: 'static, -{ - runtime: QueryRuntime, - family: QueryFamily, F::Record>, - selection: QuerySelection, F::Record>, -} - -impl std::fmt::Debug for RevisionedFamily -where - F: TypedQueryFamily + 'static, - F::Key: 'static, - F::Record: 'static, -{ - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter - .debug_struct("RevisionedFamily") - .field("family", &self.family) - .finish_non_exhaustive() - } -} - -impl RevisionedFamily -where - F: TypedQueryFamily + 'static, - F::Key: 'static, - F::Record: 'static, -{ - #[cfg(test)] - pub(crate) fn new(runtime: &QueryRuntime, name: &'static str) -> Self { - let family = runtime - .family_with_equality(name, F::MAX_TERMINALS, record_equal::) - .expect("compiler query families have unique stable names"); - let selection = family.selection(); - Self { - runtime: runtime.clone(), - family, - selection, - } - } - - /// A family registered CONTENT-ADDRESSED (RUE-1112): its records are pure - /// functions of their keys alone, which is the sole minting authority for - /// the exact-terminal adoption capability - /// ([`rue_query::QueryFamily::adoptable_terminal`]). The parse family - /// qualifies: an ordinary key is the exact source content, table order, - /// and presentation; a successor key pins the published immutable lineage - /// plus its exact appended segment. - pub(crate) fn new_content_addressed(runtime: &QueryRuntime, name: &'static str) -> Self { - let family = runtime - .content_addressed_family_with_equality(name, F::MAX_TERMINALS, record_equal::) - .expect("compiler query families have unique stable names"); - let selection = family.selection(); - Self { - runtime: runtime.clone(), - family, - selection, - } - } - - fn key(&mut self, key: F::Key) -> CompatibilityKey { - CompatibilityKey { key } - } - - /// The runtime family handle, for a computing query that declares a - /// dependency on one of this family's terminals (RUE-1112): requesting a - /// key through it records a real dependency edge, so red/green validation - /// and leases flow through the terminal. - pub(crate) fn family_handle(&self) -> QueryFamily, F::Record> { - self.family.clone() - } - - /// The currently selected terminal, for identity assertions. - #[cfg(test)] - pub(crate) fn selected_terminal(&self) -> Option>> { - self.selection.current().cloned() - } - - /// The last good terminal itself — the exact capability a successor - /// computation records as its predecessor dependency (RUE-1112). - pub(crate) fn last_good_terminal(&self) -> Option<&Arc>> { - self.selection.last_good() - } - - pub(crate) fn prepare(&mut self, key: F::Key) -> PreparedRevisionedQuery { - PreparedRevisionedQuery { - runtime: self.runtime.clone(), - family: self.family.clone(), - key: self.key(key), - } - } - - pub(crate) fn select(&mut self, attempt: &QueryRequestAttempt) { - if attempt.execution() == RequestExecution::Aborted { - self.selection.clear_current(); - } - if let Some(terminal) = attempt.terminal() { - self.selection - .publish(terminal) - .expect("selected terminal belongs to its compiler family"); - // The selection root now protects the terminal. End the attempt's - // bridge lease at once: this attempt is about to be ledgered - // (`attempt_view`) and kept for up to 256 completed requests, and a - // lingering bridge pin would retain the terminal for that whole life. - // Releasing only after `publish` established the successor keeps - // protection continuous — the terminal is never left unpinned. - attempt.release_result_lease(); - } - } - - #[cfg(test)] - pub(crate) fn request( - &mut self, - revision: Revision, - key: F::Key, - compute: impl FnOnce(&rue_query::QueryContext) -> Result, - ) -> Arc> { - let key = self.key(key); - let attempt = Arc::new(self.runtime.request( - &self.family, - revision, - key, - CancellationToken::new(), - |context| { - let record = compute(context)?; - assert!( - F::record_is_consistent(&record), - "typed query record key does not match its terminal artifact revision" - ); - let kind = match F::terminal_kind(&record) { - TerminalKind::Success => QueryTerminalKind::Success, - TerminalKind::Failure => QueryTerminalKind::Failure, - }; - Ok(QueryOutput::success(record).with_terminal_kind(kind)) - }, - )); - self.select(&attempt); - attempt - } - - pub(crate) fn attempt_view( - &mut self, - id: AttemptId, - attempt: Arc>, - work: QueryStructuralWork, - ) -> Arc { - let origin = AttemptId(attempt.origin_request_id()); - let runtime_observations = attempt - .dependencies() - .iter() - .cloned() - .map(RuntimeObservation::Dependency) - .chain( - attempt - .inputs() - .iter() - .cloned() - .map(RuntimeObservation::Input), - ) - .collect::>() - .into(); - let runtime_work = attempt.work().to_vec().into(); - Arc::new(RuntimeAttemptView:: { - id, - origin, - attempt, - work, - runtime_observations, - runtime_work, - }) - } - - #[cfg(test)] - pub(crate) fn current_record(&self) -> Option<&F::Record> { - let terminal = self.selection.current()?; - match terminal.outcome() { - rue_query::QueryOutcome::Success(record) => Some(record), - rue_query::QueryOutcome::Failure(_) => unreachable!("compiler families retain records"), - } - } - - pub(crate) fn last_good_record(&self) -> Option<&F::Record> { - let terminal = self.selection.last_good()?; - match terminal.outcome() { - rue_query::QueryOutcome::Success(record) => Some(record), - rue_query::QueryOutcome::Failure(_) => unreachable!("compiler families retain records"), - } - } - - pub(crate) fn retention(&self) -> rue_query::FamilyRetention { - self.family.retention() - } - - pub(crate) fn protected_count(&self) -> usize { - match (self.selection.current(), self.selection.last_good()) { - (Some(current), Some(last_good)) if Arc::ptr_eq(current, last_good) => 1, - (Some(_), Some(_)) => 2, - (Some(_), None) | (None, Some(_)) => 1, - (None, None) => 0, - } - } - - pub(crate) fn origin_attempt_ids(&self) -> impl Iterator + '_ { - let mut origins = self - .family - .retained_origin_request_ids() - .into_iter() - .map(AttemptId) - .collect::>(); - origins.extend( - [self.selection.current(), self.selection.last_good()] - .into_iter() - .flatten() - .map(|terminal| AttemptId(terminal.origin_request_id())), - ); - origins.into_iter() - } - - pub(crate) fn retained_aborted_len(&self) -> usize { - // Runtime aborts are owned by the diagnostic/metrics attempt index; - // this family retains no separate aborted-attempt history. - 0 - } - - fn any_retained_key(&self, predicate: impl FnMut(&F::Key) -> bool) -> bool { - let mut predicate = predicate; - self.family.any_retained_key(|key| predicate(&key.key)) - } -} - -pub(crate) struct PreparedRevisionedQuery -where - F: TypedQueryFamily + 'static, - F::Key: 'static, - F::Record: 'static, -{ - runtime: QueryRuntime, - family: QueryFamily, F::Record>, - key: CompatibilityKey, -} - -impl PreparedRevisionedQuery -where - F: TypedQueryFamily + 'static, - F::Key: 'static, - F::Record: 'static, -{ - pub(crate) fn execute( - self, - revision: Revision, - origin: AttemptId, - compute: impl FnOnce(&rue_query::QueryContext) -> Result, - ) -> Arc> { - Arc::new(self.runtime.request_with_origin( - &self.family, - revision, - self.key, - CancellationToken::new(), - Some(origin.0), - |context| { - let record = compute(context)?; - assert!(F::record_is_consistent(&record)); - let kind = match F::terminal_kind(&record) { - TerminalKind::Success => QueryTerminalKind::Success, - TerminalKind::Failure => QueryTerminalKind::Failure, - }; - Ok(QueryOutput::success(record).with_terminal_kind(kind)) - }, - )) - } -} - #[derive(Debug)] struct RuntimeAttemptView { id: AttemptId, @@ -446,10 +171,6 @@ where self.origin } - fn dependencies(&self) -> &[crate::query_graph::ObservedDependency] { - &[] - } - fn runtime_observations(&self) -> &[RuntimeObservation] { &self.runtime_observations } @@ -476,7 +197,6 @@ where } } -#[derive(Debug)] pub(crate) struct RevisionedQueryDatabase { runtime: QueryRuntime, next_revision: u64, @@ -677,7 +397,25 @@ pub(crate) struct RevisionedQueryDatabase { /// `dependencies()`. #[cfg(test)] provider_probe: QueryFamily, - pub(crate) parse: RevisionedFamily, + parse: QueryFamily< + CompatibilityKey, + super::session::ParseQueryRecord, + >, + parse_selection: QuerySelection< + CompatibilityKey, + super::session::ParseQueryRecord, + >, +} + +impl std::fmt::Debug for RevisionedQueryDatabase { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter + .debug_struct("RevisionedQueryDatabase") + .field("next_revision", &self.next_revision) + .field("next_source_stamp", &self.next_source_stamp) + .field("parse_retention", &self.parse.retention()) + .finish_non_exhaustive() + } } /// Cumulative provider-op observation counters, one per §4 fact family. The @@ -13970,8 +13708,17 @@ impl RevisionedQueryDatabase { .is_ok(), "BodyTransaction evaluator is installed once" ); + let parse = runtime + .content_addressed_family_with_equality( + "compiler.parse", + super::session::ParseQuery::MAX_TERMINALS, + record_equal::, + ) + .expect("the Parse family has one canonical name"); + let parse_selection = parse.selection(); Self { - parse: RevisionedFamily::new_content_addressed(&runtime, "compiler.parse"), + parse, + parse_selection, runtime: runtime.clone(), next_revision: 1, next_source_stamp: 1, @@ -14267,13 +14014,76 @@ impl RevisionedQueryDatabase { } pub(crate) fn current_parse_revision(&self) -> Option { - let terminal = self.parse.selection.current()?; + let terminal = self.parse_selection.current()?; let rue_query::QueryOutcome::Success(record) = terminal.outcome() else { unreachable!("Parse publishes typed records") }; Some(record.runtime_revision()) } + /// The runtime family handle, for successor parsing to observe an exact + /// adopted predecessor terminal. + pub(crate) fn parse_family( + &self, + ) -> QueryFamily< + CompatibilityKey, + super::session::ParseQueryRecord, + > { + self.parse.clone() + } + + /// The currently selected parse terminal, for identity assertions. + #[cfg(test)] + pub(crate) fn selected_parse_terminal( + &self, + ) -> Option>> { + self.parse_selection.current().cloned() + } + + /// The exact last-good parse terminal retained by the runtime selection + /// root. Successor parsing adopts this terminal as a true runtime edge. + pub(crate) fn last_good_parse_terminal( + &self, + ) -> Option<&Arc>> { + self.parse_selection.last_good() + } + + pub(crate) fn last_good_parse_record(&self) -> Option<&super::session::ParseQueryRecord> { + let terminal = self.parse_selection.last_good()?; + match terminal.outcome() { + rue_query::QueryOutcome::Success(record) => Some(record), + rue_query::QueryOutcome::Failure(_) => None, + } + } + + pub(crate) fn request_parse( + &self, + revision: Revision, + origin: AttemptId, + key: super::session::ParseQueryKey, + compute: impl FnOnce(&QueryContext) -> Result, + ) -> Arc> { + Arc::new(self.runtime.request_with_origin( + &self.parse, + revision, + CompatibilityKey { key }, + CancellationToken::new(), + Some(origin.0), + |context| { + let record = compute(context)?; + assert!( + super::session::ParseQuery::record_is_consistent(&record), + "parse record key does not match its terminal artifact revision" + ); + let kind = match super::session::ParseQuery::terminal_kind(&record) { + TerminalKind::Success => QueryTerminalKind::Success, + TerminalKind::Failure => QueryTerminalKind::Failure, + }; + Ok(QueryOutput::success(record).with_terminal_kind(kind)) + }, + )) + } + /// Revision pin for semantic work. Import discovery republishes the exact /// same module leaves together with its observation leaves, so semantic /// queries must run on that successor revision when one exists. @@ -17730,22 +17540,98 @@ impl RevisionedQueryDatabase { &mut self, attempt: &QueryRequestAttempt, ) { - self.parse.select(attempt); + if attempt.execution() == RequestExecution::Aborted { + self.parse_selection.clear_current(); + } + if let Some(terminal) = attempt.terminal() { + self.parse_selection + .publish(terminal) + .expect("selected terminal belongs to the Parse family"); + // Publication establishes the runtime selection root before the + // request bridge lease ends, so the terminal stays protected while + // the diagnostic attempt index retains this request. + attempt.release_result_lease(); + } // Exact source stamps live exactly as long as a parse memo key (or the // current request before selection). They are never independently FIFO // evicted while a terminal can still observe the stamp. self.source_stamps.retain(|(source, _)| { self.parse - .any_retained_key(|key| key.pinned_source() == Some(source)) + .any_retained_key(|key| key.key.pinned_source() == Some(source)) }); debug_assert!(self.source_stamps.len() <= self.parse.retention().memo_nodes); } + pub(crate) fn parse_attempt_view( + &self, + id: AttemptId, + attempt: Arc>, + work: QueryStructuralWork, + ) -> Arc { + let origin = AttemptId(attempt.origin_request_id()); + let runtime_observations = attempt + .dependencies() + .iter() + .cloned() + .map(RuntimeObservation::Dependency) + .chain( + attempt + .inputs() + .iter() + .cloned() + .map(RuntimeObservation::Input), + ) + .collect::>() + .into(); + let runtime_work = attempt.work().to_vec().into(); + Arc::new(RuntimeAttemptView:: { + id, + origin, + attempt, + work, + runtime_observations, + runtime_work, + }) + } + + pub(crate) fn parse_origin_attempt_ids(&self) -> impl Iterator + '_ { + let mut origins = self + .parse + .retained_origin_request_ids() + .into_iter() + .map(AttemptId) + .collect::>(); + origins.extend( + [ + self.parse_selection.current(), + self.parse_selection.last_good(), + ] + .into_iter() + .flatten() + .map(|terminal| AttemptId(terminal.origin_request_id())), + ); + origins.into_iter() + } + + pub(crate) fn parse_retained_aborted_len(&self) -> usize { + // Abort history belongs to the diagnostic/metrics attempt index. + 0 + } + pub(crate) fn parse_retention(&self) -> crate::typed_query_store::QueryStoreRetention { let retention = self.parse.retention(); + let protected = match ( + self.parse_selection.current(), + self.parse_selection.last_good(), + ) { + (Some(current), Some(last_good)) if Arc::ptr_eq(current, last_good) => 1, + (Some(_), Some(_)) => 2, + (Some(_), None) | (None, Some(_)) => 1, + (None, None) => 0, + }; crate::typed_query_store::QueryStoreRetention { retained: retention.terminals, - protected: self.parse.protected_count(), + protected, pinned: 0, tombstones: 0, evictions: self.runtime.metrics().evictions as usize, @@ -27499,50 +27385,6 @@ fn main() -> i32 { assert_eq!(execution(&recovered), RequestExecution::Computed); } - #[derive(Debug, Clone, PartialEq, Eq, Hash)] - struct Key(&'static str); - - #[derive(Debug, Clone)] - struct Record { - key: Key, - value: u64, - diagnostic_payload: u64, - failed: bool, - } - - #[derive(Debug)] - struct Family; - - impl TypedQueryFamily for Family { - type Key = Key; - type Record = Record; - const MAX_TERMINALS: usize = 4; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.failed { - TerminalKind::Failure - } else { - TerminalKind::Success - } - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.value == right.value - } - - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.diagnostic_payload == right.diagnostic_payload - } - - fn record_is_consistent(record: &Self::Record) -> bool { - !record.key.0.is_empty() - } - } - #[test] fn wide_root_imports_form_one_exact_compiler_frontier() { let source = r#" @@ -28081,217 +27923,6 @@ fn main() -> i32 { ); } - #[test] - fn selected_state_shim_uses_runtime_attempts_and_last_good_publication() { - let runtime = QueryRuntime::new(1); - let input = InputIdentity::new("test", "leaf"); - let first_revision = Revision::new(1, 1); - let second_revision = Revision::new(2, 2); - runtime - .publish_revision(first_revision, [(input.clone(), 1)]) - .unwrap(); - runtime - .publish_revision(second_revision, [(input.clone(), 2)]) - .unwrap(); - let mut family = RevisionedFamily::::new(&runtime, "compiler.test-family"); - - let computed = family.request(first_revision, Key("key"), |context| { - context.input(input.clone())?; - Ok(Record { - key: Key("key"), - value: 7, - diagnostic_payload: 11, - failed: false, - }) - }); - assert_eq!(execution(&computed), RequestExecution::Computed); - assert_eq!(computed.inputs().len(), 1); - - let reused = family.request(first_revision, Key("key"), |_| { - panic!("the exact keyed terminal must be runtime-reused") - }); - assert_eq!(execution(&reused), RequestExecution::Reused); - assert!(reused.work().is_empty()); - - let failed = family.request(second_revision, Key("key"), |context| { - context.input(input)?; - Ok(Record { - key: Key("key"), - value: 9, - diagnostic_payload: 12, - failed: true, - }) - }); - assert_eq!( - failed.terminal().unwrap().kind(), - QueryTerminalKind::Failure - ); - assert!(family.current_record().unwrap().failed); - assert_eq!(family.last_good_record().unwrap().value, 7); - - let aborted = family.request(second_revision, Key("abort"), |_| Err(QueryAbort::Canceled)); - assert_eq!(execution(&aborted), RequestExecution::Aborted); - assert!(family.current_record().is_none()); - assert_eq!(family.last_good_record().unwrap().value, 7); - - let recovered = family.request(second_revision, Key("recovered"), |context| { - context.input(InputIdentity::new("test", "leaf"))?; - Ok(Record { - key: Key("recovered"), - value: 10, - diagnostic_payload: 13, - failed: false, - }) - }); - assert_eq!(execution(&recovered), RequestExecution::Computed); - assert_eq!(family.current_record().unwrap().value, 10); - assert_eq!(family.last_good_record().unwrap().value, 10); - assert_eq!(family.retention().memo_nodes, 2); - } - - #[test] - fn aborted_attempt_view_projects_runtime_work_without_forging_typed_work() { - let runtime = QueryRuntime::new(1); - let input = InputIdentity::new("test", "prefix"); - let revision = Revision::new(10, 1); - runtime - .publish_revision(revision, [(input.clone(), 3)]) - .unwrap(); - let mut family = RevisionedFamily::::new(&runtime, "compiler.abort-prefix"); - let prepared = family.prepare(Key("prefix")); - let attempt = prepared.execute(revision, AttemptId(77), |context| { - context.input(input)?; - context.record_work(rue_query::WorkItem::new("runtime-prefix", 2)); - Err(QueryAbort::Canceled) - }); - family.select(&attempt); - let structural = QueryStructuralWork::Parse(crate::ParsedModulesWork { - modules_considered: 1, - ..crate::ParsedModulesWork::default() - }); - let view = family.attempt_view(AttemptId(77), attempt.clone(), structural.clone()); - assert_eq!(view.origin_id(), AttemptId(77)); - assert_eq!( - view.outcome(), - AttemptOutcomeKind::Aborted(AbortedQueryReason::Canceled) - ); - assert!(view.dependencies().is_empty()); - assert_eq!(view.runtime_observations().len(), 1); - assert!(matches!( - &view.runtime_observations()[0], - RuntimeObservation::Input(input) if input.stamp == 3 - )); - assert_eq!(view.work(), &QueryStructuralWork::None); - assert_eq!( - view.runtime_work(), - &[(Arc::::from("runtime-prefix"), 2)] - ); - assert_eq!(attempt.work(), &[(Arc::::from("runtime-prefix"), 2)]); - assert_eq!(family.retained_aborted_len(), 0); - } - - #[test] - fn runtime_frozen_origin_survives_reuse_without_a_peer_registry() { - let runtime = QueryRuntime::new(1); - let revision = Revision::new(11, 1); - runtime.publish_revision(revision, []).unwrap(); - let mut family = RevisionedFamily::::new(&runtime, "compiler.origin"); - let computed = family - .prepare(Key("origin")) - .execute(revision, AttemptId(41), |_| { - Ok(Record { - key: Key("origin"), - value: 1, - diagnostic_payload: 1, - failed: false, - }) - }); - family.select(&computed); - assert_eq!(computed.origin_request_id(), 41); - let reused = family - .prepare(Key("origin")) - .execute(revision, AttemptId(42), |_| { - panic!("retained terminal must be reused") - }); - assert_eq!(reused.execution(), RequestExecution::Reused); - assert_eq!(reused.origin_request_id(), 41); - let view = family.attempt_view(AttemptId(42), reused, QueryStructuralWork::None); - assert_eq!(view.origin_id(), AttemptId(41)); - assert_eq!( - family.origin_attempt_ids().collect::>(), - vec![AttemptId(41)] - ); - } - - // Selecting a result ends the attempt-carried bridge lease immediately, so a - // completed attempt retained in the session ledger no longer pins its - // terminal. Without the release the ledgered attempt would keep the terminal - // retained for the record's whole life, defeating the retention cap. - #[test] - fn selecting_a_result_releases_the_attempt_bridge_lease_before_ledgering() { - let runtime = QueryRuntime::new(1); - let revision = Revision::new(20, 1); - runtime.publish_revision(revision, []).unwrap(); - let mut family = RevisionedFamily::::new(&runtime, "compiler.bridge-release"); - - // Produce and select a result, then hold the attempt alive for the rest - // of the test — mimicking the bounded attempt ledger retaining it. - let bridged = family - .prepare(Key("bridged")) - .execute(revision, AttemptId(1), |_| { - Ok(Record { - key: Key("bridged"), - value: 1, - diagnostic_payload: 1, - failed: false, - }) - }); - family.select(&bridged); - - // Move the selection to a succession of other results. The bridged - // terminal is no longer selection-protected; if `select` released its - // bridge lease, it is now wholly unprotected even though `bridged` lives. - for (offset, name) in ["f0", "f1", "f2", "f3", "f4", "f5"].into_iter().enumerate() { - let value = 100 + offset as u64; - let filler = family.prepare(Key(name)).execute( - revision, - AttemptId(10 + offset as u64), - move |_| { - Ok(Record { - key: Key(name), - value, - diagnostic_payload: 1, - failed: false, - }) - }, - ); - family.select(&filler); - } - - // The bridged terminal was evicted under the cap: a fresh request - // recomputes it. Under the pre-fix bridge leak the still-alive ledgered - // attempt would keep pinning it and this request would reuse instead. - let recomputed = family - .prepare(Key("bridged")) - .execute(revision, AttemptId(99), |_| { - Ok(Record { - key: Key("bridged"), - value: 1, - diagnostic_payload: 1, - failed: false, - }) - }); - assert_eq!( - recomputed.execution(), - RequestExecution::Computed, - "a selected result's bridge lease must end at selection, not at attempt drop" - ); - - // The bridged attempt was alive throughout: the release was driven by - // selection, not by the attempt dropping. - assert!(bridged.terminal().is_some()); - } - // ----------------------------------------------------------------------- // RUE-1091 slice 3a — widened module name index + lookup families. // diff --git a/crates/rue-compiler/src/scaling_harness.rs b/crates/rue-compiler/src/scaling_harness.rs index 05aecd9e4..5534a2242 100644 --- a/crates/rue-compiler/src/scaling_harness.rs +++ b/crates/rue-compiler/src/scaling_harness.rs @@ -498,16 +498,6 @@ fn assert_warm_fresh_parity( format!("{:?}", fresh.functions()), "{label}: full AIR/CFG public artifacts diverged" ); - assert_eq!( - warm.durable_ordinary_body_payloads(), - fresh.durable_ordinary_body_payloads(), - "{label}: durable ordinary bodies diverged" - ); - assert_eq!( - warm.durable_specialized_body_payloads(), - fresh.durable_specialized_body_payloads(), - "{label}: durable specialized bodies diverged" - ); assert_eq!( format!("{:?}", warm.warnings()), format!("{:?}", fresh.warnings()), @@ -1110,6 +1100,7 @@ fn import_source( ) } +#[allow(dead_code)] fn rooted_demand_locality_source( leaf_value: i32, unrelated_value: i32, @@ -1990,84 +1981,6 @@ fn correctness_oracle_import_edit_compares_imported_body_and_linked_bytes() { ); } -#[test] -fn rooted_demand_in_process_host_pins_warm_edit_locality() { - let options = CompileOptions::default(); - let run_edit = |leaf_value, unrelated_value| { - let (baseline, baseline_context, baseline_reads) = rooted_demand_locality_source(1, 10, 1); - let (edited, edited_context, edited_reads) = - rooted_demand_locality_source(leaf_value, unrelated_value, 2); - let mut session = CompilerSession::new(); - // Stay entirely on the rooted import-input protocol. Mixing this with - // the ordinary update-token namespace is tracked separately by RUE-1202. - close_import_source(&mut session, &baseline, baseline_context, baseline_reads); - session - .semantic(&options) - .expect("rooted-demand baseline compiles"); - session - .unstable_dependency_baseline(&options, None) - .expect("rooted-demand baseline dependency manifest is available"); - close_import_source(&mut session, &edited, edited_context, edited_reads); - let semantic_record_start = session.unstable_metrics().semantic_record_count(); - session - .semantic(&options) - .expect("rooted-demand warm edit compiles"); - let work = session - .unstable_metrics() - .semantic_work_json(semantic_record_start); - let count = |path: &[&str]| { - path.iter() - .fold(&work, |value, key| &value[*key]) - .as_u64() - .unwrap() as usize - }; - [ - count(&["body_analyses_computed"]), - count(&["body_analyses_reused"]), - count(&["declaration_reuse", "durable_records_compared"]), - count(&["declaration_reuse", "durable_records_reused"]), - ] - }; - let reachable = run_edit(2, 10); - let unrelated = run_edit(1, 11); - let mut report = Report::new( - "rooted-demand warm locality: in-process import-input host \ - (synthetic accepted reads; no filesystem re-observation)", - ); - for (label, measured, computed, reused) in [ - ("reachable imported leaf body edit", reachable, 1, 1), - ("unrelated imported body edit", unrelated, 0, 2), - ] { - assert_eq!( - measured[0], computed, - "{label}: rooted-demand warm computed-body count changed" - ); - assert_eq!( - measured[1], reused, - "{label}: rooted-demand warm reused-body count changed" - ); - assert_eq!( - measured[2], 4, - "{label}: rooted-demand declaration-context record count changed" - ); - assert_eq!( - measured[3], 4, - "{label}: every unchanged declaration-context record must be reusable" - ); - report.push(Row::Met { - label: format!( - "{label}: computed={} reused={} declaration-context records={}/{} reused", - measured[0], measured[1], measured[3], measured[2], - ), - }); - } - report.emit(); -} - -// --------------------------------------------------------------------------- -// Specialization rows -// --------------------------------------------------------------------------- - #[test] fn specialization_breadth_compiles_depth_fails_e1200() { // Breadth is not depth (RUE-1083). Many shallow specializations compile; an diff --git a/crates/rue-compiler/src/session.rs b/crates/rue-compiler/src/session.rs index 1e0456563..ec9d3100f 100644 --- a/crates/rue-compiler/src/session.rs +++ b/crates/rue-compiler/src/session.rs @@ -4,17 +4,14 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::sync::{Arc, Mutex}; use rue_air::{DeclarationBindingWork, SemanticBindingManifestWork}; -use rue_span::Span; -use sha2::{Digest, Sha256}; use crate::{ BoundDefinitionSet, BoundDefinitionWork, CanonicalImportGraph, CanonicalImportGraphValidation, - CanonicalImportResolution, CanonicalMergeWork, CanonicalMergedProgram, CanonicalRirOutput, - CanonicalRirWork, CanonicalSemanticOutput, CanonicalSemanticWork, CodegenInputDescriptor, - CompileError, CompileErrors, CompileOptions, CompileWarning, DurableDeclarationSemantic, - ErrorKind, ModuleResolutionInputs, ParseInvalidationSummary, ParsedModulesWork, - SemanticInputDescriptor, SourceRevision, SourceSnapshot, StableDefinitionKey, - StableDefinitionKind, StableDefinitionNamespace, StableOptLevel, StablePreviewFeatures, + CanonicalMergeWork, CanonicalMergedProgram, CanonicalRirOutput, CanonicalRirWork, + CanonicalSemanticOutput, CanonicalSemanticWork, CodegenInputDescriptor, CompileError, + CompileErrors, CompileOptions, CompileWarning, ErrorKind, ModuleResolutionInputs, + ParseInvalidationSummary, ParsedModulesWork, SemanticInputDescriptor, SourceRevision, + SourceSnapshot, StablePreviewFeatures, canonical_lower::project_module_rirs_with_work, canonical_merge::merge_parsed_modules_reusing_indexes, canonical_semantic::{ @@ -31,9 +28,8 @@ use crate::diagnostic_attempt_store::{ FrontendDiagnosticSnapshot, ImportDiagnosticInputDescriptor, }; use crate::typed_query_store::{ - AbortedQueryReason, AttemptExecution as QueryAttemptExecution, AttemptView, - QUERY_TERMINAL_RETENTION_LIMIT, TerminalKind, TypedEquivalentLookupFamily, TypedQueryFamily, - TypedQueryStore, TypedSecondaryLookupFamily, + AbortedQueryReason, AttemptExecution as QueryAttemptExecution, AttemptOutcomeKind, AttemptView, + QUERY_TERMINAL_RETENTION_LIMIT, TerminalKind, TypedQueryFamily, }; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -88,10 +84,6 @@ pub struct FrontendRetentionMetrics { pub diagnostic_source_attempts: usize, /// Source bytes across those distinct attempts (shared stages count once). pub diagnostic_source_bytes: usize, - /// Distinct dependency manifests strongly owned by all session caches. - pub dependency_manifests: usize, - /// Recent semantic invalidation plans strongly owned by the session. - pub invalidation_plans: usize, } #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -117,10 +109,6 @@ pub struct CompilerSessionWork { pub diagnostic_publications: usize, pub diagnostic_reuses: usize, pub diagnostic_invalidations: usize, - pub dependency_manifests: FrontendQueryWork, - pub dependency_manifest_records_visited: usize, - pub dependency_manifest_import_records_visited: usize, - pub invalidation_plans: FrontendQueryWork, pub declaration_reuse_plans: usize, pub durable_records_compared: usize, pub durable_records_reused: usize, @@ -150,8 +138,6 @@ pub(crate) enum QueryStructuralWork { Rir(CanonicalRirWork), Semantic(Box), Definition(Box), - DependencyManifest(Box), - Invalidation(SemanticInvalidationWork), } #[derive(Debug, Clone)] @@ -215,7 +201,6 @@ impl QueryAttemptIndex { fn project_lifecycle(work: &mut FrontendQueryWork, attempt: &dyn AttemptView) { let _ = ( attempt.outcome(), - attempt.dependencies(), attempt.runtime_observations(), attempt.runtime_work(), attempt.work(), @@ -236,12 +221,48 @@ struct QueryComputationGuard { id: AttemptId, family: &'static str, attempt: Option>, - dependencies: Vec, diagnostics: Option>, structural: QueryStructuralWork, cancel_requested: bool, } +/// Lifecycle-only attempt retained for canonical phases that publish directly +/// into the revisioned runtime rather than a compatibility typed-query store. +#[derive(Debug)] +struct InstrumentedQueryAttempt { + id: AttemptId, + execution: QueryAttemptExecution, + outcome: AttemptOutcomeKind, + diagnostics: Option>, + work: QueryStructuralWork, +} + +impl AttemptView for InstrumentedQueryAttempt { + fn id(&self) -> AttemptId { + self.id + } + + fn execution(&self) -> QueryAttemptExecution { + self.execution + } + + fn outcome(&self) -> AttemptOutcomeKind { + self.outcome + } + + fn origin_id(&self) -> AttemptId { + self.id + } + + fn work(&self) -> &QueryStructuralWork { + &self.work + } + + fn diagnostics(&self) -> Option<&Arc> { + self.diagnostics.as_ref() + } +} + impl QueryComputationGuard { fn started(&mut self) {} @@ -249,21 +270,10 @@ impl QueryComputationGuard { self.structural = structural; } - fn structural(&self) -> QueryStructuralWork { - self.structural.clone() - } - fn bind(&mut self, attempt: Arc) { self.attempt = Some(attempt); } - fn observe( - &mut self, - dependencies: impl IntoIterator, - ) { - self.dependencies.extend(dependencies); - } - fn attach_diagnostics(&mut self, diagnostics: Arc) { self.diagnostics = Some(diagnostics); } @@ -274,16 +284,37 @@ impl QueryComputationGuard { fn finish( self, - _execution: QueryAttemptExecution, + execution: QueryAttemptExecution, _reuse_origin: Option, - _result: &Result, - _structural: QueryStructuralWork, + result: &Result, + structural: QueryStructuralWork, ) -> AttemptId { if let Some(attempt) = self.attempt { self.sink .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .index(self.family, attempt); + } else { + let outcome = if result.is_ok() { + AttemptOutcomeKind::Success + } else if self.cancel_requested { + AttemptOutcomeKind::Aborted(AbortedQueryReason::Canceled) + } else { + AttemptOutcomeKind::Failure + }; + self.sink + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .index( + self.family, + Arc::new(InstrumentedQueryAttempt { + id: self.id, + execution, + outcome, + diagnostics: self.diagnostics, + work: structural, + }), + ); } self.id } @@ -304,13 +335,6 @@ struct CompilerSessionMetrics { } impl CompilerSessionMetrics { - fn allocate_attempt_id(&self) -> AttemptId { - self.attempts - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner) - .allocate() - } - fn work(&self) -> &CompilerSessionWork { &self.aggregate } @@ -328,7 +352,6 @@ impl CompilerSessionMetrics { id, family: Q::NAME, attempt: None, - dependencies: Vec::new(), diagnostics: None, structural: QueryStructuralWork::None, cancel_requested: false, @@ -347,7 +370,6 @@ impl CompilerSessionMetrics { id, family, attempt: None, - dependencies: Vec::new(), diagnostics: None, structural: QueryStructuralWork::None, cancel_requested: false, @@ -424,18 +446,12 @@ impl CompilerSessionMetrics { QueryStructuralWork::Definition(record) => { self.aggregate.definition_records.push((**record).clone()); } - QueryStructuralWork::DependencyManifest(work) => { - self.aggregate.dependency_manifest_records_visited += - work.definition_records_visited; - self.aggregate.dependency_manifest_import_records_visited += - work.import_records_visited; - } - QueryStructuralWork::Invalidation(_) => {} QueryStructuralWork::Merge(_) | QueryStructuralWork::Rir(_) => {} } } #[cfg(test)] + #[allow(dead_code)] fn attempts(&self) -> Vec { self.attempts .lock() @@ -452,22 +468,12 @@ impl CompilerSessionMetrics { self.aggregate.last_invalidation = invalidation; } - fn project_dependency_invalidations( - &mut self, - graph: &crate::query_graph::QueryGraph, - changed_existing_revision: bool, - ) { + fn project_dependency_invalidations(&mut self, changed_existing_revision: bool) { if changed_existing_revision { self.aggregate.downstream_invalidations += 1; } - let semantic = graph.invalidation_count::(); - let definitions = graph.invalidation_count::(); - self.aggregate.semantic_entries_invalidated += - semantic.saturating_sub(self.projected_semantic_invalidations); - self.aggregate.definition_entries_invalidated += - definitions.saturating_sub(self.projected_definition_invalidations); - self.projected_semantic_invalidations = semantic; - self.projected_definition_invalidations = definitions; + self.projected_semantic_invalidations = 0; + self.projected_definition_invalidations = 0; self.aggregate.last_merge = CanonicalMergeWork::default(); self.aggregate.last_rir = CanonicalRirWork::default(); self.aggregate.semantic_entries = 0; @@ -500,573 +506,6 @@ impl CompilerSessionMetrics { } } -/// Maximum number of recent invalidation plans owned by a frontend session. -/// -/// Each entry strongly owns both input manifests. Oldest insertion is evicted -/// first; weak references are intentionally not used because a plan's -/// dependency inputs must remain sound for as long as the cached plan exists. -pub const FRONTEND_INVALIDATION_PLAN_RETENTION_LIMIT: usize = 8; - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct SemanticDependencyManifestWork { - pub definition_records_visited: usize, - pub import_records_visited: usize, - pub declaration_type_events_translated: usize, - pub declaration_type_call_head_events_translated: usize, - pub builtin_type_call_head_inputs_translated: usize, - pub named_const_events_translated: usize, - pub implicit_named_destructor_events_translated: usize, - pub body_owner_events_translated: usize, - pub body_named_events_translated: usize, - pub body_dependency_records_built: usize, - pub durable_bodies: crate::DurableBodyWork, - pub extra_rir_instructions_visited: usize, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableFreeFunctionDependency { - pub caller: StableDefinitionKey, - pub callee: StableDefinitionKey, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableImplicitNamedDestructorDependency { - pub source: StableDefinitionKey, - pub target: StableDefinitionKey, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableDeclarationTypeDependency { - pub source: StableDefinitionKey, - pub target: StableDefinitionKey, - pub kind: rue_air::DeclarationTypeDependencyKind, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableDeclarationTypeCallHeadDependency { - pub source: StableDefinitionKey, - pub callable: StableDefinitionKey, - pub kind: rue_air::DeclarationTypeDependencyKind, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableBuiltinTypeCallHeadInput { - pub source: StableDefinitionKey, - pub builtin: rue_air::BuiltinTypeCallHead, - pub kind: rue_air::DeclarationTypeDependencyKind, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum StableNamedConstDependencyTarget { - ValueConst(StableDefinitionKey), - FreeFunction(StableDefinitionKey), - NamedType(StableDefinitionKey), - ModuleBinding(StableDefinitionKey), -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableNamedConstDependency { - pub source: StableDefinitionKey, - pub target: StableNamedConstDependencyTarget, -} - -/// Complete stable inputs observed for one successfully analyzed ordinary body. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StableBodyDependencyInputRecord { - owner: StableDefinitionKey, - fingerprint: StableDefinitionInputFingerprint, - target: crate::Target, - preview_features: StablePreviewFeatures, - direct_dependency_inputs: Arc<[StableDefinitionInputFingerprint]>, - builtin_type_call_heads: Arc<[StableBuiltinTypeCallHeadInput]>, - blockers: Arc<[SemanticDependencyBlocker]>, -} - -impl StableBodyDependencyInputRecord { - pub fn owner(&self) -> &StableDefinitionKey { - &self.owner - } - pub fn fingerprint(&self) -> &StableDefinitionInputFingerprint { - &self.fingerprint - } - #[cfg_attr(not(test), allow(dead_code))] - pub fn reusable_boundary_supported(&self) -> bool { - self.blockers.is_empty() - } -} - -/// Versioned digest of one immutable semantic input fragment. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableDefinitionFingerprint([u8; 32]); - -impl StableDefinitionFingerprint { - #[cfg(test)] - pub(crate) fn for_test(byte: u8) -> Self { - Self([byte; 32]) - } -} - -/// Precision of the parser-authored source partition represented by a -/// definition fingerprint. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum StableDefinitionFingerprintPrecision { - SignatureAndBody, - SignatureAndInitializer, - /// All declaration bytes are semantic signature input and there is no - /// independently executable payload. - ExactSignature, -} - -/// Immutable, relocation-independent inputs for one stable definition. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StableDefinitionInputFingerprint { - /// Schema version for persisted consumers. Bump when domains or partition - /// semantics change. - pub schema_version: u16, - pub key: StableDefinitionKey, - /// Stable identity and visibility metadata, excluding source locations. - pub declaration: StableDefinitionFingerprint, - /// Signature/header bytes, or the full declaration under conservative - /// precision. - pub signature: StableDefinitionFingerprint, - /// Function/method/destructor body or const initializer when exact parser - /// boundaries are available. - pub body_or_initializer: Option, - pub precision: StableDefinitionFingerprintPrecision, -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum StableModuleImportDependency { - Resolved { - importer: crate::ModuleId, - normalized_specifier: Arc, - target: crate::ModuleId, - }, - Missing { - importer: crate::ModuleId, - normalized_specifier: Arc, - }, - Ambiguous { - importer: crate::ModuleId, - normalized_specifier: Arc, - file_module: crate::ModuleId, - directory_module: crate::ModuleId, - }, -} - -/// A semantic dependency surface whose captured edges may be incomplete. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum SemanticDependencySurface { - BodyOwner, - FreeFunctionCall, - GenericNamedMethodCall, - ImplicitNamedDestructor, - DeclarationType, - DeclarationTypeCallHead, - SupportedTypeCallHead, - NamedValueConst, -} - -/// The production evidence which prevents a dependency surface from being trusted. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum SemanticDependencyIncompleteReason { - AnonymousBodyOwnerUnavailable, - CallerEndpointUnavailable, - GenericSubstitutionIdentityUnavailable, - AnonymousDropOwnerUnavailable, - ResolvedTypeIdentityUnavailable, - TypeCallHeadIdentityUnavailable, - UnsupportedDynamicTypeCallHead, - ConstEndpointUnavailable, -} - -/// A deterministic, stable-keyed reason that semantic reuse must fail closed. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SemanticDependencyBlocker { - owner: Option, - surface: SemanticDependencySurface, - reason: SemanticDependencyIncompleteReason, -} - -impl SemanticDependencyBlocker { - pub fn owner(&self) -> Option<&StableDefinitionKey> { - self.owner.as_ref() - } - pub fn surface(&self) -> SemanticDependencySurface { - self.surface - } - pub fn reason(&self) -> SemanticDependencyIncompleteReason { - self.reason - } -} - -/// A non-empty, sorted set of stable dependency blockers. -/// -/// Construction is centralized here so the retained slice is the one -/// canonical value used for planning, accessors, and presentation. -#[derive(Debug, Clone, PartialEq, Eq)] -struct NonEmptySemanticDependencyBlockers { - blockers: Arc<[SemanticDependencyBlocker]>, -} - -impl NonEmptySemanticDependencyBlockers { - fn from_blockers(mut blockers: Vec) -> Option { - blockers.sort(); - blockers.dedup(); - (!blockers.is_empty()).then(|| Self { - blockers: blockers.into(), - }) - } - - fn as_slice(&self) -> &[SemanticDependencyBlocker] { - &self.blockers - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum DurableBodyCandidateState { - Complete, - Incomplete(NonEmptySemanticDependencyBlockers), -} - -impl DurableBodyCandidateState { - fn from_blockers(blockers: Vec) -> Self { - match NonEmptySemanticDependencyBlockers::from_blockers(blockers) { - Some(blockers) => Self::Incomplete(blockers), - None => Self::Complete, - } - } - - fn blockers(&self) -> &[SemanticDependencyBlocker] { - match self { - Self::Complete => &[], - Self::Incomplete(blockers) => blockers.as_slice(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum SemanticDependencyGraphState { - Complete, - Incomplete(NonEmptySemanticDependencyBlockers), -} - -impl SemanticDependencyGraphState { - fn from_blockers(blockers: Vec) -> Self { - match NonEmptySemanticDependencyBlockers::from_blockers(blockers) { - Some(blockers) => Self::Incomplete(blockers), - None => Self::Complete, - } - } - - fn blockers(&self) -> &[SemanticDependencyBlocker] { - match self { - Self::Complete => &[], - Self::Incomplete(blockers) => blockers.as_slice(), - } - } - - fn is_complete(&self) -> bool { - matches!(self, Self::Complete) - } - - fn surface_complete(&self, surface: SemanticDependencySurface) -> bool { - match self { - Self::Complete => true, - Self::Incomplete(blockers) => blockers - .as_slice() - .iter() - .all(|blocker| blocker.surface != surface), - } - } - - /// Feed every incomplete surface into invalidation planning. The match on - /// `SemanticDependencySurface` is deliberately exhaustive: adding a new - /// surface cannot compile until this fold accounts for it. - fn fold_planning_blockers(&self, blockers: &mut BTreeSet) { - let Self::Incomplete(incomplete) = self else { - return; - }; - for blocker in incomplete.as_slice() { - match blocker.surface { - SemanticDependencySurface::BodyOwner - | SemanticDependencySurface::FreeFunctionCall - | SemanticDependencySurface::GenericNamedMethodCall - | SemanticDependencySurface::ImplicitNamedDestructor - | SemanticDependencySurface::DeclarationType - | SemanticDependencySurface::DeclarationTypeCallHead - | SemanticDependencySurface::SupportedTypeCallHead - | SemanticDependencySurface::NamedValueConst => { - blockers.insert(blocker.clone()); - } - } - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct NonEmptyDefinitionFailures { - failures: Arc<[rue_error::CompileError]>, -} - -impl NonEmptyDefinitionFailures { - fn from_errors(errors: &CompileErrors) -> Self { - assert!( - !errors.is_empty(), - "failed stable-definition query must carry at least one error" - ); - Self { - failures: errors.as_slice().to_vec().into(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum SemanticDefinitionUniverseIncompleteReason { - StableDefinitionsFailed(NonEmptyDefinitionFailures), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -enum SemanticDefinitionUniverseState { - Complete, - Incomplete(SemanticDefinitionUniverseIncompleteReason), -} - -#[derive(Clone, PartialEq, Eq)] -pub struct SemanticDependencyInputManifest { - input: SemanticInputDescriptor, - imports: CanonicalImportGraph, - definitions: Arc<[StableDefinitionKey]>, - definition_fingerprints: Arc<[StableDefinitionInputFingerprint]>, - module_imports: Arc<[StableModuleImportDependency]>, - free_function_dependencies: Arc<[StableFreeFunctionDependency]>, - implicit_named_destructor_dependencies: Arc<[StableImplicitNamedDestructorDependency]>, - declaration_type_dependencies: Arc<[StableDeclarationTypeDependency]>, - declaration_type_call_head_dependencies: Arc<[StableDeclarationTypeCallHeadDependency]>, - builtin_type_call_head_inputs: Arc<[StableBuiltinTypeCallHeadInput]>, - named_const_dependencies: Arc<[StableNamedConstDependency]>, - body_dependencies: Arc<[StableBodyDependencyInputRecord]>, - durable_ordinary_bodies: Arc<[crate::DurableOrdinaryBody]>, - /// Completeness of durable body candidates, including owner-specific - /// blockers. This is distinct from whole-graph invalidation completeness. - durable_body_candidate_state: DurableBodyCandidateState, - dependency_graph_state: SemanticDependencyGraphState, - definition_universe_state: SemanticDefinitionUniverseState, - work: SemanticDependencyManifestWork, -} - -impl SemanticDependencyInputManifest { - /// Cheap hash discriminant consistent with `PartialEq`. `Eq` compares the - /// full contents, so hashing only these lengths is sound: equal manifests - /// share them, and any collision is resolved by exact `Eq`. - fn hash_discriminant(&self, state: &mut H) { - use std::hash::Hash; - self.definitions.len().hash(state); - self.definition_fingerprints.len().hash(state); - self.module_imports.len().hash(state); - self.body_dependencies.len().hash(state); - } -} - -impl std::fmt::Debug for SemanticDependencyInputManifest { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter - .debug_struct("SemanticDependencyInputManifest") - .field("input", &self.input) - .field("imports", &self.imports) - .field("definitions", &self.definitions) - .field("definition_fingerprints", &self.definition_fingerprints) - .field("module_imports", &self.module_imports) - .field( - "free_function_dependencies", - &self.free_function_dependencies, - ) - .field( - "implicit_named_destructor_dependencies", - &self.implicit_named_destructor_dependencies, - ) - .field( - "declaration_type_dependencies", - &self.declaration_type_dependencies, - ) - .field( - "declaration_type_call_head_dependencies", - &self.declaration_type_call_head_dependencies, - ) - .field( - "builtin_type_call_head_inputs", - &self.builtin_type_call_head_inputs, - ) - .field("named_const_dependencies", &self.named_const_dependencies) - .field("body_dependencies", &self.body_dependencies) - .field("durable_ordinary_bodies", &self.durable_ordinary_bodies) - .field("body_dependency_blockers", &self.body_dependency_blockers()) - .field("dependency_blockers", &self.dependency_blockers()) - .field( - "definition_universe_complete", - &self.definition_universe_complete(), - ) - .field("work", &self.work) - .finish() - } -} - -impl SemanticDependencyInputManifest { - pub fn input(&self) -> &SemanticInputDescriptor { - &self.input - } - pub fn imports(&self) -> &CanonicalImportGraph { - &self.imports - } - pub fn definitions(&self) -> &[StableDefinitionKey] { - &self.definitions - } - pub fn definition_fingerprints(&self) -> &[StableDefinitionInputFingerprint] { - &self.definition_fingerprints - } - pub fn module_imports(&self) -> &[StableModuleImportDependency] { - &self.module_imports - } - pub fn free_function_dependencies(&self) -> &[StableFreeFunctionDependency] { - &self.free_function_dependencies - } - pub fn free_function_caller_dependencies_complete(&self) -> bool { - self.surface_complete(SemanticDependencySurface::FreeFunctionCall) - } - pub fn implicit_named_destructor_dependencies( - &self, - ) -> &[StableImplicitNamedDestructorDependency] { - &self.implicit_named_destructor_dependencies - } - pub fn implicit_named_destructor_dependencies_complete(&self) -> bool { - self.surface_complete(SemanticDependencySurface::ImplicitNamedDestructor) - } - pub fn declaration_type_dependencies(&self) -> &[StableDeclarationTypeDependency] { - &self.declaration_type_dependencies - } - pub fn declaration_type_dependencies_complete(&self) -> bool { - self.surface_complete(SemanticDependencySurface::DeclarationType) - } - pub fn declaration_type_call_head_dependencies( - &self, - ) -> &[StableDeclarationTypeCallHeadDependency] { - &self.declaration_type_call_head_dependencies - } - pub fn declaration_type_call_head_dependencies_complete(&self) -> bool { - self.surface_complete(SemanticDependencySurface::DeclarationTypeCallHead) - } - pub fn builtin_type_call_head_inputs(&self) -> &[StableBuiltinTypeCallHeadInput] { - &self.builtin_type_call_head_inputs - } - pub fn supported_type_call_heads_complete(&self) -> bool { - self.surface_complete(SemanticDependencySurface::SupportedTypeCallHead) - } - pub fn named_const_dependencies(&self) -> &[StableNamedConstDependency] { - &self.named_const_dependencies - } - pub fn body_dependencies(&self) -> &[StableBodyDependencyInputRecord] { - &self.body_dependencies - } - /// Explicitly unstable equality status for durable-cache instrumentation. - pub fn unstable_durable_artifact_status(&self) -> crate::unstable::DurableArtifactStatus { - crate::unstable::DurableArtifactStatus::from_debug(&self.durable_ordinary_bodies) - } - pub fn body_dependency_blockers(&self) -> &[SemanticDependencyBlocker] { - self.durable_body_candidate_state.blockers() - } - pub fn named_value_const_dependencies_complete(&self) -> bool { - self.surface_complete(SemanticDependencySurface::NamedValueConst) - } - pub fn semantic_dependency_graph_complete(&self) -> bool { - self.dependency_graph_state.is_complete() - } - pub fn dependency_blockers(&self) -> &[SemanticDependencyBlocker] { - self.dependency_graph_state.blockers() - } - pub fn definition_universe_complete(&self) -> bool { - matches!( - self.definition_universe_state, - SemanticDefinitionUniverseState::Complete - ) - } - #[cfg(test)] - pub(crate) fn work(&self) -> SemanticDependencyManifestWork { - self.work - } - /// Return owned counters for unstable dependency-manifest instrumentation. - pub fn unstable_metrics(&self) -> crate::unstable::DependencyManifestMetrics { - crate::unstable::DependencyManifestMetrics::from_work(self.work) - } - - fn surface_complete(&self, surface: SemanticDependencySurface) -> bool { - self.dependency_graph_state.surface_complete(surface) - } -} - -/// A reason why semantic results cannot soundly be reused across two manifests. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum SemanticFullInvalidationReason { - RootChanged, - ModuleImportsChanged, - TargetChanged, - PreviewFeaturesChanged, - IncompleteDefinitionUniverse, - IncompleteDependencyGraph(Arc<[SemanticDependencyBlocker]>), -} - -/// Explicit work performed while planning semantic invalidation. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct SemanticInvalidationWork { - pub definition_fingerprints_compared: usize, - pub dependency_edges_visited: usize, - pub reverse_closure_nodes_visited: usize, - pub extra_rir_instructions_visited: usize, -} - -/// Immutable, stable-keyed invalidation decision for two semantic input manifests. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SemanticInvalidationScope { - Full { - reasons: Arc<[SemanticFullInvalidationReason]>, - }, - Incremental, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SemanticInvalidationPlan { - scope: SemanticInvalidationScope, - added: Arc<[StableDefinitionKey]>, - removed: Arc<[StableDefinitionKey]>, - changed: Arc<[StableDefinitionKey]>, - invalidated: Arc<[StableDefinitionKey]>, - reusable: Arc<[StableDefinitionKey]>, - work: SemanticInvalidationWork, -} - -impl SemanticInvalidationPlan { - pub fn scope(&self) -> &SemanticInvalidationScope { - &self.scope - } - pub fn added(&self) -> &[StableDefinitionKey] { - &self.added - } - pub fn removed(&self) -> &[StableDefinitionKey] { - &self.removed - } - pub fn changed(&self) -> &[StableDefinitionKey] { - &self.changed - } - pub fn invalidated(&self) -> &[StableDefinitionKey] { - &self.invalidated - } - pub fn reusable(&self) -> &[StableDefinitionKey] { - &self.reusable - } - pub fn work(&self) -> SemanticInvalidationWork { - self.work - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct ImportGraphInputDescriptor { pub(crate) sources: SourceRevision, @@ -1245,6 +684,10 @@ impl CompilerSessionUpdate { #[derive(Debug, Default)] pub struct CompilerSession { identity: Arc<()>, + /// Test-only differential-oracle perturbation requested through the + /// unstable test bridge. It corrupts a canonical projection at the next + /// observation point without reviving a retired selected-result store. + oracle_fault: Option, /// Protocol context only while the typed import-closure query is open. /// Closed attempts live exclusively in their plan or closure terminal. open_discovery: Option>, @@ -1317,30 +760,6 @@ pub struct CompilerSession { definition_shard_baseline: Option, metrics: CompilerSessionMetrics, diagnostics: DiagnosticAttemptStore, - /// Explicit durable baseline for the next semantic attempt (RUE-1143). - /// - /// `None` on every ordinary compile, in which case a semantic attempt reuses - /// from the last-good semantic record. No production path sets it. - /// - /// It is deliberately NOT `#[cfg(test)]`. The reuse-baseline selection this - /// feeds is the most correctness-sensitive lookup in the compiler, and it - /// previously existed twice: production read the last-good record, while a - /// `cfg(test)` shadow field took precedence under test. Tests therefore - /// validated a selection order production never executed — underneath the - /// differential oracle, which is the defense for exactly that class of bug. - /// Keeping the slot unconditional means both configurations compile one - /// selection expression; only whether anything ever fills it differs. - durable_baseline_override: Option, -} - -/// An explicit durable baseline supplied in place of the last-good record. -/// -/// Tests use this to drive a specific — including deliberately stale, corrupt, -/// or partially-populated — durable cache through the production reuse path, -/// rather than through a parallel branch compiled only under `cfg(test)`. -#[derive(Debug, Default, Clone)] -struct DurableBaselineOverride { - durable_declaration_cache: Option, } #[cfg(test)] @@ -1518,146 +937,47 @@ impl From for SemanticRequestControl { } } -/// The single typed frontend query database owned by `CompilerSession`. +/// Canonical frontend runtime state owned by `CompilerSession`. /// -/// Query algorithms stay in the session; this value owns terminals and their -/// dependency/reverse-dependency state only. +/// Import staging and closure artifacts are thin projections of the revisioned +/// source/import frontier. They retain presentation-facing immutable results, +/// never a second selected-query authority. #[derive(Debug)] struct FrontendQueryDatabase { - /// Canonical Phase 1 execution substrate. The legacy stores below are - /// removed family-by-family as callers move through its selected-state shim. revisioned: crate::revisioned_query_database::RevisionedQueryDatabase, - // RUE-1033 COMPATIBILITY: these selected-state plan/closure records retain - // legacy diagnostic attempts only. Syntax, name lookup, exact occurrence - // resolution, and module lowering are authoritative runtime families. - import_plans: TypedQueryStore, - import_closures: TypedQueryStore, - import_diagnostics: TypedQueryStore, - merge: TypedQueryStore, - rir: TypedQueryStore, - semantic: TypedQueryStore, - definitions: TypedQueryStore, - manifests: TypedQueryStore, - invalidation_plans: TypedQueryStore, - graph: crate::query_graph::QueryGraph, - import_plan_inputs: crate::query_graph::TypedLeafStore, - import_closure_inputs: crate::query_graph::TypedLeafStore, - source_inputs: crate::query_graph::TypedLeafStore, - import_inputs: crate::query_graph::TypedLeafStore, - target_inputs: crate::query_graph::TypedLeafStore, - preview_inputs: crate::query_graph::TypedLeafStore, - optimization_inputs: crate::query_graph::TypedLeafStore, + discovery_attempt: Option>, + last_good_discovery: Option>, + prior_discovery: Option>, + oracle_import_fault: Option>, + direct_import_diagnostic: Option>, } impl Default for FrontendQueryDatabase { fn default() -> Self { Self { revisioned: crate::revisioned_query_database::RevisionedQueryDatabase::default(), - import_plans: TypedQueryStore::default(), - import_closures: TypedQueryStore::default(), - import_diagnostics: TypedQueryStore::default(), - merge: TypedQueryStore::default(), - rir: TypedQueryStore::default(), - semantic: TypedQueryStore::default(), - definitions: TypedQueryStore::default(), - manifests: TypedQueryStore::default(), - invalidation_plans: TypedQueryStore::default(), - graph: crate::query_graph::QueryGraph::default(), - import_plan_inputs: crate::query_graph::TypedLeafStore::new( - QUERY_TERMINAL_RETENTION_LIMIT, - ), - import_closure_inputs: crate::query_graph::TypedLeafStore::new( - QUERY_TERMINAL_RETENTION_LIMIT, - ), - source_inputs: crate::query_graph::TypedLeafStore::new(QUERY_TERMINAL_RETENTION_LIMIT), - import_inputs: crate::query_graph::TypedLeafStore::new(QUERY_TERMINAL_RETENTION_LIMIT), - target_inputs: crate::query_graph::TypedLeafStore::new(QUERY_TERMINAL_RETENTION_LIMIT), - preview_inputs: crate::query_graph::TypedLeafStore::new(QUERY_TERMINAL_RETENTION_LIMIT), - optimization_inputs: crate::query_graph::TypedLeafStore::new( - QUERY_TERMINAL_RETENTION_LIMIT, - ), + discovery_attempt: None, + last_good_discovery: None, + prior_discovery: None, + oracle_import_fault: None, + direct_import_diagnostic: None, } } } impl FrontendQueryDatabase { - fn publish_source(&mut self, source: ExactSourceInput) -> bool { - let previous = self.source_inputs.selected(&self.graph); - let current = self.source_inputs.publish(&mut self.graph, source); - previous.is_some_and(|previous| previous != current) - } - - /// Select `source` as the current exact source WITHOUT disappearing the - /// predecessor leaf (RUE-1112). A strictly-additive successor adoption - /// leaves the predecessor's immutable leaf live, so every retained - /// terminal that correctly depends on it stays valid — nothing is - /// invalidated or re-walked; new publications simply observe the new - /// leaf. Ordinary updates keep the disappearing [`Self::publish_source`], - /// whose invalidation is the real contract for replaced sources. - fn publish_source_additive(&mut self, source: ExactSourceInput) { - self.source_inputs.publish_retained(&mut self.graph, source); - } - - fn publish_import_graph(&mut self, imports: CanonicalImportGraph) { - self.import_inputs.publish(&mut self.graph, imports); - } - - fn publish_request_inputs(&mut self, options: &CompileOptions) { - self.target_inputs - .publish_retained(&mut self.graph, options.target); - self.preview_inputs.publish_retained( - &mut self.graph, - StablePreviewFeatures::new(&options.preview_features), - ); - self.optimization_inputs - .publish_retained(&mut self.graph, options.opt_level.into()); + fn record_discovery_attempt(&mut self, artifact: Arc) { + if let Some(previous) = self.discovery_attempt.replace(artifact.clone()) { + if previous.source_revision() != artifact.source_revision() { + self.prior_discovery = Some(previous); + } + } + if artifact.status == ImportDiscoveryRevisionStatus::ClosedValid { + self.last_good_discovery = Some(artifact); + } } } -#[derive(Debug, Clone)] -struct DurableDeclarationCache { - semantics: Arc<[DurableDeclarationSemantic]>, -} - -#[derive(Debug, Clone)] -struct InvalidationPlanQueryKey { - previous: Arc, - current: Arc, -} - -impl PartialEq for InvalidationPlanQueryKey { - fn eq(&self, other: &Self) -> bool { - (Arc::ptr_eq(&self.previous, &other.previous) || self.previous == other.previous) - && (Arc::ptr_eq(&self.current, &other.current) || self.current == other.current) - } -} - -impl Eq for InvalidationPlanQueryKey {} - -impl std::hash::Hash for InvalidationPlanQueryKey { - fn hash(&self, state: &mut H) { - // `Eq` compares the full manifest contents, so any subset of those - // fields is a valid hash source: equal keys necessarily agree on the - // discriminant, and hash collisions between distinct keys resolve - // through exact `Eq` in the memo map. A cheap length discriminant keeps - // this low-cardinality control family off a full-manifest hash. - self.previous.hash_discriminant(state); - self.current.hash_discriminant(state); - } -} - -#[derive(Debug, Clone)] -struct InvalidationPlanCacheEntry { - key: InvalidationPlanQueryKey, - plan: Arc, -} - -#[derive(Debug, Clone)] -struct DependencyManifestCacheEntry { - key: DependencyManifestQueryKey, - result: Result, CompileErrors>, -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) enum ParseQueryKey { /// Ordinary content-addressed parsing: keyed on the exact source content, @@ -1813,228 +1133,60 @@ impl TypedQueryFamily for ParseQuery { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum ImportPlanQueryKey { - /// Ordinary content-addressed staging: keyed on the exact inputs, so an - /// unchanged program recompiled in a fresh session hits the same memoized - /// terminal it does today. - Ordinary(Box), - /// Trusted-toolchain successor staging (RUE-1112): keyed on the published - /// lineage identity being staged plus the exact successor delta. The - /// published revision identity is session-unique and immutably bound to its - /// leaf view, so it stands in for the full predecessor content without - /// hashing or comparing predecessor entries; the delta names the appended - /// module revisions exactly. Identities are never fingerprints (ADR-0066): - /// a revision id is a published immutable identity, not a content digest. - Successor { - revision: crate::ImportInputRevision, - delta: Arc<[crate::ModuleRevision]>, - policy_version: u32, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct OrdinaryImportPlanKey { - source: ExactSourceInput, - context: crate::ImportDiscoveryContext, - policy_version: u32, - accepted_reads: crate::AcceptedReadManifest, - carried_ledger: crate::ImportObservationLedger, -} - -impl ImportPlanQueryKey { - /// The exact source revision an Ordinary key pins; a Successor key pins its - /// sources through the published revision identity instead. - fn pinned_source_revision(&self) -> Option<&crate::SourceRevision> { - match self { - Self::Ordinary(key) => Some(&key.source.revision), - Self::Successor { .. } => None, - } - } -} - -#[derive(Debug, Clone)] -struct ImportPlanQueryRecord { - key: ImportPlanQueryKey, - result: Result, - diagnostics: Arc, - attempted_artifact: Option>, -} - #[derive(Debug)] -struct ImportPlanQuery; - -impl TypedQueryFamily for ImportPlanQuery { - type Key = ImportPlanQueryKey; - type Record = ImportPlanQueryRecord; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.result == right.result - } - - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - diagnostic_batches_equal(&left.diagnostics, &right.diagnostics) - } - - fn diagnostics(record: &Self::Record) -> Option<&Arc> { - Some(&record.diagnostics) - } - - fn record_is_consistent(record: &Self::Record) -> bool { - match record.key.pinned_source_revision() { - Some(revision) => record.diagnostics.source_revision() == revision, - None => true, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum ImportClosureQueryKey { - /// Ordinary content-addressed closure (unchanged warm-reuse semantics). - Ordinary(Box), - /// Trusted-toolchain successor closure (RUE-1112): keyed on the published - /// lineage identity being closed plus the exact successor delta (see the - /// plan key's Successor variant for the identity discipline). - Successor { - revision: crate::ImportInputRevision, - delta: Arc<[crate::ModuleRevision]>, - policy_version: u32, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct OrdinaryImportClosureKey { - source: ExactSourceInput, - context: crate::ImportDiscoveryContext, - policy_version: u32, - accepted_reads: crate::AcceptedReadManifest, - plan: crate::ImportDiscoveryPlan, - ledger: crate::ImportObservationLedger, -} - -impl ImportClosureQueryKey { - fn pinned_source_revision(&self) -> Option<&crate::SourceRevision> { - match self { - Self::Ordinary(key) => Some(&key.source.revision), - Self::Successor { .. } => None, - } - } -} - -#[derive(Debug, Clone)] -struct ImportClosureQueryRecord { - key: ImportClosureQueryKey, - result: Result, CompileErrors>, - artifact: Arc, - diagnostics: Arc, +struct DefinitionComputation { + result: Result, CompileErrors>, + binding: DeclarationBindingWork, + manifest: SemanticBindingManifestWork, + issuance: BoundDefinitionWork, } +/// Metric-family markers for canonical projections; they do not own terminals. #[derive(Debug)] -struct ImportClosureQuery; - -impl TypedQueryFamily for ImportClosureQuery { - type Key = ImportClosureQueryKey; - type Record = ImportClosureQueryRecord; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - match (&left.result, &right.result) { - (Ok(left), Ok(right)) => left.input() == right.input() && left.graph() == right.graph(), - (Err(left), Err(right)) => compile_errors_equal(left, right), - _ => false, - } - } - - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - diagnostic_batches_equal(&left.diagnostics, &right.diagnostics) - } +struct ImportDiagnosticQuery; +#[derive(Debug)] +struct MergeQuery; +#[derive(Debug)] +struct RirQuery; +#[derive(Debug)] +struct SemanticQuery; +#[derive(Debug)] +struct DefinitionQuery; - fn diagnostics(record: &Self::Record) -> Option<&Arc> { - Some(&record.diagnostics) - } +macro_rules! session_query_metrics_family { + ($query:ty, $name:literal, $field:ident) => { + impl SessionQueryMetricsFamily for $query { + const NAME: &'static str = $name; - fn record_is_consistent(record: &Self::Record) -> bool { - let mutual = - record.artifact.snapshot().source_revision() == record.diagnostics.source_revision(); - match record.key.pinned_source_revision() { - Some(revision) => { - record.artifact.snapshot().source_revision() == revision - && record.diagnostics.source_revision() == revision - && match &record.result { - Ok(graph) => &graph.input().sources == revision, - Err(_) => true, - } - } - None => { - mutual - && match &record.result { - Ok(graph) => { - &graph.input().sources == record.artifact.snapshot().source_revision() - } - Err(_) => true, - } + fn projection(work: &mut CompilerSessionWork) -> &mut FrontendQueryWork { + &mut work.$field } } - } -} - -#[derive(Debug, Clone)] -struct MergeCacheEntry { - key: MergeQueryKey, - result: Result, CompileErrors>, - diagnostics: Arc, + }; } -#[derive(Debug, Clone)] -struct RirCacheEntry { - key: RirQueryKey, - result: Result, CompileErrors>, - merged: Option>, - diagnostics: Arc, -} +session_query_metrics_family!(ImportsMetricsQuery, "imports", imports); +session_query_metrics_family!( + ImportDiagnosticQuery, + "import-diagnostics", + import_diagnostics +); +session_query_metrics_family!(MergeQuery, "merge", merge); +session_query_metrics_family!(RirQuery, "rir", rir); +session_query_metrics_family!(SemanticQuery, "semantic", semantic); +session_query_metrics_family!(DefinitionQuery, "definitions", definitions); +/// Explicit compiler inputs read by a terminal attempt. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct MergeQueryKey { - source: ExactSourceInput, - presentation: Option>, +pub(crate) struct ExactSourceInput { + revision: SourceRevision, + metadata: crate::SourceMetadata, } -#[derive(Debug)] -struct MergeQuery; - fn compile_errors_equal(left: &CompileErrors, right: &CompileErrors) -> bool { left.iter().eq(right.iter()) } -fn query_control_error(error: crate::typed_query_store::BeginSelectedError) -> T { - std::panic::panic_any(error) -} - fn diagnostic_batches_equal( left: &FrontendDiagnosticSnapshot, right: &FrontendDiagnosticSnapshot, @@ -2045,13959 +1197,7176 @@ fn diagnostic_batches_equal( && left.warnings == right.warnings } -impl TypedQueryFamily for MergeQuery { - type Key = MergeQueryKey; - type Record = MergeCacheEntry; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key +impl ExactSourceInput { + pub(crate) fn new(snapshot: &SourceSnapshot) -> Self { + Self { + revision: snapshot.source_revision().clone(), + metadata: snapshot.metadata().clone(), + } } +} - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } +fn compute_stable_definitions( + merged: &CanonicalMergedProgram, + options: &CompileOptions, + imports: &CanonicalImportGraph, + semantic: &CanonicalSemanticOutput, +) -> DefinitionComputation { + let _ = (options, imports); + let definitions = semantic + .body_owner_issuer() + .projected_for_source_revision(merged.ast().source_revision()); + let work = semantic.work(); + let binding = work.binding; + let manifest = work.manifest; + let issuance = definitions.work(); + let result = Ok(Arc::new(definitions)); + DefinitionComputation { + result, + binding, + manifest, + issuance, } +} - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - match (&left.result, &right.result) { - // Exact source bytes and metadata plus presentation order are the - // complete deterministic merge input. Work counters and compact - // allocation identities are intentionally not part of equality. - (Ok(_), Ok(_)) => left.key == right.key, - (Err(left), Err(right)) => compile_errors_equal(left, right), - _ => false, +impl CompilerSession { + #[cfg(test)] + pub(crate) fn with_query_concurrency(workers: usize) -> Self { + let mut session = Self::default(); + session.queries.revisioned = + crate::revisioned_query_database::RevisionedQueryDatabase::with_query_concurrency( + workers, + ); + session + } + /// Perturb one canonical observation for the in-tree differential oracle. + #[doc(hidden)] + pub(crate) fn inject_stale_query_for_oracle( + &mut self, + fault: crate::unstable::DifferentialOracleFault, + ) -> bool { + match fault { + crate::unstable::DifferentialOracleFault::Semantic => { + self.oracle_fault = Some(fault); + true + } + crate::unstable::DifferentialOracleFault::Diagnostic => { + let Some(source) = self.published_snapshot.clone() else { + return false; + }; + let errors = CompileErrors::from(CompileError::without_span( + ErrorKind::InternalError("differential diagnostic fault".into()), + )); + // This intentional oracle-only corruption must be selected as a + // distinct canonical attempt. `publish_diagnostics` correctly + // reuses an equal RIR key, which would hide this oracle fault. + let snapshot = Arc::new(FrontendDiagnosticSnapshot { + source: source.clone(), + stage: FrontendDiagnosticIdentity::Rir(source.source_revision().clone()), + provenance: DiagnosticAttemptProvenance::Canonical, + errors: errors.iter().cloned().collect::>().into(), + warnings: Arc::from([]), + }); + self.diagnostics.select_snapshot(&snapshot); + self.refresh_retention_metrics(); + true + } + crate::unstable::DifferentialOracleFault::Import => { + let Some(stale) = self.queries.prior_discovery.clone() else { + return false; + }; + let Some(current) = self.queries.discovery_attempt.as_ref() else { + return false; + }; + if stale.source_revision() == current.source_revision() { + return false; + } + self.queries.oracle_import_fault = Some(stale); + true + } } } - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - diagnostic_batches_equal(&left.diagnostics, &right.diagnostics) + pub fn new() -> Self { + Self::default() } - fn diagnostics(record: &Self::Record) -> Option<&Arc> { - Some(&record.diagnostics) + fn resume_canceled_query( + &mut self, + guard: &mut QueryComputationGuard, + payload: Box, + ) -> ! { + match guard.family { + "import-diagnostics" | "merge" | "rir" | "semantic" | "definitions" | "imports" + | "parse" => {} + family => unreachable!("unknown query guard family {family}"), + } + self.metrics.synchronize(); + std::panic::resume_unwind(payload) } - fn record_is_consistent(record: &Self::Record) -> bool { - let artifact_matches = match &record.result { - Ok(merged) => merged.ast().source_revision() == &record.key.source.revision, - Err(_) => true, - }; - artifact_matches - && record.diagnostics.source_revision() == &record.key.source.revision - && record.diagnostics.identity() == &FrontendDiagnosticIdentity::Merge + fn cancel_merge_at_commit_boundary(&mut self) -> bool { + #[cfg(test)] + return std::mem::take(&mut self.cancel_merge_before_commit); + #[cfg(not(test))] + false } -} - -impl TypedSecondaryLookupFamily for MergeQuery { - type SecondaryKey = SourceRevision; - fn matches_secondary(record: &Self::Record, key: &Self::SecondaryKey) -> bool { - &record.key.source.revision == key + /// Select the accepted import topology for semantic construction. + /// + /// Import-bearing revisions must come from the atomically adopted + /// discovery artifact. A direct session remains usable for an import-free + /// snapshot by supplying the uniquely valid empty graph; it may never + /// reconstruct resolved imports from paths or environment state. + fn accepted_semantic_import_graph(&self) -> Result { + let program = self.published.as_ref().ok_or_else(no_published_program)?; + let graph = if !program.import_directives().is_empty() { + let committed = self.committed_import_graph()?; + if &committed.input().sources != program.source_revision() { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "committed import graph belongs to a foreign source revision".into(), + ), + ))); + } + committed.graph().clone() + } else { + crate::import_graph::import_free_canonical_graph(program.as_ref())? + }; + Ok(graph) + } + pub fn published(&self) -> Option { + self.published.as_ref().cloned().map(crate::SyntaxView::new) } -} -impl TypedEquivalentLookupFamily for MergeQuery { - fn rekey_equivalent(record: &Self::Record, key: Self::Key) -> Option { - if record.result.is_err() || !record.diagnostics.is_success() { - return None; - } - let mut record = record.clone(); - record.key = key; - Some(record) + #[cfg(test)] + pub(crate) fn import_discovery_plan( + &self, + context: crate::ImportDiscoveryContext, + ) -> crate::CompileResult { + let program = self.published.as_ref().ok_or_else(|| { + CompileError::without_span(ErrorKind::InvalidCompilerInput( + "import discovery requires a successfully parsed staging revision".into(), + )) + })?; + crate::ImportDiscoveryPlan::new(program, context) } -} -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct RirQueryKey { - source: SourceRevision, -} + pub(crate) fn published_owner(&self) -> Option<&Arc> { + self.published.as_ref() + } -#[derive(Debug)] -struct RirQuery; + /// Begins one fresh rooted external-input request with granular immutable + /// module source, accepted-read provenance, and observation leaves. + /// Successor carry is available only through batch publication. + pub(crate) fn begin_import_input_request( + &mut self, + snapshot: &SourceSnapshot, + context: crate::ImportDiscoveryContext, + accepted_reads: crate::AcceptedReadManifest, + ) -> crate::CompileResult { + // A fresh observation generation invalidates any outstanding + // trusted-toolchain continuation and successor-delta authority (RUE-1112). + self.continuation = None; + self.successor_delta_nonce = None; + self.queries + .revisioned + .begin_import_inputs(snapshot, context, accepted_reads) + } -impl TypedQueryFamily for RirQuery { - type Key = RirQueryKey; - type Record = RirCacheEntry; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; + pub(crate) fn import_demand_frontier_for_roots( + &mut self, + revision: crate::ImportInputRevision, + plan: &crate::ImportDiscoveryPlan, + mode: crate::ImportDemandMode, + roots: &crate::ImportDemandRoots, + ) -> crate::CompileResult { + self.queries + .revisioned + .import_frontier(revision, plan, mode, roots) + } - fn key(record: &Self::Record) -> &Self::Key { - &record.key + /// Publishes exactly one compiler-produced rooted host batch as one + /// successor immutable revision. + pub(crate) fn publish_import_observation_batch( + &mut self, + frontier: &crate::ImportDemandFrontier, + snapshot: &SourceSnapshot, + accepted_reads: crate::AcceptedReadManifest, + observations: Vec, + ) -> crate::CompileResult { + self.queries.revisioned.publish_import_batch( + frontier, + snapshot, + accepted_reads, + observations, + ) } - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } + /// Returns the immutable canonical ledger carried by one input revision. + pub(crate) fn import_observation_ledger( + &self, + revision: crate::ImportInputRevision, + ) -> crate::CompileResult { + self.queries.revisioned.import_ledger(revision) } - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - match (&left.result, &right.result) { - (Ok(left), Ok(right)) => Arc::ptr_eq(left, right) || left.structurally_eq(right), - (Err(left), Err(right)) => compile_errors_equal(left, right), - _ => false, + /// Stages the current compiler-published import-input revision. + /// + /// Snapshot, context, accepted reads, and carried observations are read as + /// one immutable compiler-owned view. A host can only advance this state by + /// publishing a frontier batch, so it cannot substitute a peer plan or + /// closure record. + pub(crate) fn stage_import_input_request( + &mut self, + revision: crate::ImportInputRevision, + ) -> Result { + let Some((current, snapshot, context, accepted_reads, ledger)) = + self.queries.revisioned.current_import_view_state() + else { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "import staging requires a current compiler-published input revision".into(), + ), + ))); + }; + if current != revision { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "import staging requested a non-current compiler-published input revision" + .into(), + ), + ))); } + self.stage_import_discovery_inner(&snapshot, context, accepted_reads, ledger, None) } - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - diagnostic_batches_equal(&left.diagnostics, &right.diagnostics) + /// Cumulative import occurrences the demand frontier has rooted (RUE-1112). + /// One `ResolveImport` projection is dispatched per rooted occurrence, so the + /// delta across a trusted-toolchain re-close counts only the newly appended + /// leaves and modules newly discovered from them — never a predecessor + /// occurrence. The host driver reads this to prove the re-close does not + /// re-root the predecessor import topology. + pub(crate) fn import_frontier_roots_requested(&self) -> u64 { + self.queries.revisioned.import_frontier_roots_requested() } - fn diagnostics(record: &Self::Record) -> Option<&Arc> { - Some(&record.diagnostics) + /// Cumulative import-plan request groups constructed during staging + /// (RUE-1112). See the field docs on `import_plan_groups_constructed`. + pub(crate) fn import_plan_groups_constructed(&self) -> u64 { + self.import_plan_groups_constructed } - fn record_is_consistent(record: &Self::Record) -> bool { - (match &record.result { - Ok(output) => output.source_revision() == &record.key.source, - Err(_) => true, - }) && record.diagnostics.source_revision() == &record.key.source - && record.diagnostics.identity() - == &FrontendDiagnosticIdentity::Rir(record.key.source.clone()) - && record - .merged - .as_ref() - .is_none_or(|merged| merged.ast().source_revision() == &record.key.source) + /// See the field docs on `parse_sources_materialized`. + pub(crate) fn parse_sources_materialized(&self) -> u64 { + self.parse_sources_materialized } -} - -#[derive(Debug, Clone)] -struct DirectImportDiagnosticCacheEntry { - key: ImportDiagnosticInputDescriptor, - diagnostics: Arc, -} - -#[derive(Debug)] -struct ImportDiagnosticQuery; -impl TypedQueryFamily for ImportDiagnosticQuery { - type Key = ImportDiagnosticInputDescriptor; - type Record = DirectImportDiagnosticCacheEntry; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key + /// See the field docs on `parse_key_entries_compared`. + pub(crate) fn parse_key_entries_compared(&self) -> u64 { + self.parse_key_entries_compared } - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.diagnostics.is_success() { - TerminalKind::Success - } else { - TerminalKind::Failure - } + /// See the field docs on `parse_modules_dispatched`. + pub(crate) fn parse_modules_dispatched(&self) -> u64 { + self.parse_modules_dispatched } - fn outcome_equal(_left: &Self::Record, _right: &Self::Record) -> bool { - // This projection's value is unit; its entire observable answer is the - // attached diagnostic batch. - true + /// See the field docs on `parse_invalidation_entries_compared`. + pub(crate) fn parse_invalidation_entries_compared(&self) -> u64 { + self.parse_invalidation_entries_compared } - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - diagnostic_batches_equal(&left.diagnostics, &right.diagnostics) + /// A snapshot of the production provider-op observation counters + /// (ADR-0066 §4). + pub(crate) fn provider_observation_metrics( + &self, + ) -> crate::unstable::ProviderObservationMetrics { + self.queries.revisioned.provider_observation_metrics() } - fn diagnostics(record: &Self::Record) -> Option<&Arc> { - Some(&record.diagnostics) + /// A snapshot of the lookup-family pressure metrics (RUE-1091, ADR-0066 §4). + /// Production body publications retain their exact observed lookup + /// terminals in the session's `PublishedRootLookupLease`. + pub(crate) fn lookup_pressure_metrics(&self) -> crate::unstable::LookupPressureMetrics { + self.queries.revisioned.lookup_pressure_metrics() } - fn record_is_consistent(record: &Self::Record) -> bool { - record.diagnostics.source_revision() == record.key.source_revision() - && record.diagnostics.identity() - == &FrontendDiagnosticIdentity::Import(record.key.clone()) + /// The currently selected parse terminal, for identity assertions. + #[cfg(test)] + pub(crate) fn selected_parse_terminal( + &self, + ) -> Option>> { + self.queries.revisioned.selected_parse_terminal() } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct SemanticQueryKey { - input: CodegenInputDescriptor, - imports: CanonicalImportGraph, -} - -/// Complete semantic binding identity shared by every optimization variant. -#[derive(Debug, Clone, PartialEq, Eq)] -struct SemanticBindingLookupKey { - input: SemanticInputDescriptor, - imports: CanonicalImportGraph, -} - -#[derive(Debug, Clone)] -struct SemanticCacheEntry { - key: SemanticQueryKey, - result: Result, CompileErrors>, - rir: Option>, - diagnostics: Arc, - durable_declaration_cache: Option, - oracle_injected: bool, -} -#[derive(Debug)] -struct SemanticQuery; - -impl TypedQueryFamily for SemanticQuery { - type Key = SemanticQueryKey; - type Record = SemanticCacheEntry; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key + /// Cumulative canonical-frontier replacement events. A strictly-additive + /// successor adoption preserves the predecessor revision and contributes + /// zero; ordinary source replacement advances this counter once. + pub(crate) fn frontend_query_invalidations(&self) -> u64 { + self.metrics.work().downstream_invalidations as u64 } - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } + /// Cumulative close-time `ResolveImport` projections dispatched (RUE-1112). + pub(crate) fn exact_import_groups_dispatched(&self) -> u64 { + self.queries.revisioned.exact_import_groups_dispatched() } - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - match (&left.result, &right.result) { - // Semantic analysis is deterministic over the complete typed key. - // This is an exhaustive equality proof and excludes request-local - // compact indices, allocation identity, and work observations. - (Ok(_), Ok(_)) => left.key == right.key, - (Err(left), Err(right)) => compile_errors_equal(left, right), - _ => false, - } + /// Cumulative canonical import records reduced and validated during close + /// (RUE-1112). See the field docs on `import_close_records_reduced`. + pub(crate) fn import_close_records_reduced(&self) -> u64 { + self.import_close_records_reduced } - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - diagnostic_batches_equal(&left.diagnostics, &right.diagnostics) + /// Cumulative leaves published through the complete publication path + /// (fresh generations); scales with the program (RUE-1112). + pub(crate) fn import_view_full_leaves_published(&self) -> u64 { + self.queries.revisioned.import_view_full_leaves_published() } - fn diagnostics(record: &Self::Record) -> Option<&Arc> { - Some(&record.diagnostics) + /// Cumulative delta leaves published through the sparse successor overlay + /// path; predecessor leaves are structurally inherited and never counted + /// (RUE-1112). + pub(crate) fn import_view_overlay_leaves_published(&self) -> u64 { + self.queries + .revisioned + .import_view_overlay_leaves_published() } - fn record_is_consistent(record: &Self::Record) -> bool { - let artifact_matches = match &record.result { - Ok(output) => output.input() == &record.key.input, - Err(_) => true, - }; - let expected_stage = FrontendDiagnosticIdentity::Semantic(semantic_diagnostic_input( - &record.key.input, - record.key.imports.clone(), - )); - record.key.input.semantic.sources.root() == record.key.imports.root() - && (artifact_matches || record.oracle_injected) - && record.diagnostics.source_revision() == &record.key.input.semantic.sources - && record.diagnostics.identity() == &expected_stage + /// Cumulative predecessor ledger observations deep-cloned into successor + /// view ledgers (visible remaining cost; RUE-1112). + pub(crate) fn import_view_ledger_entries_cloned(&self) -> u64 { + self.queries.revisioned.import_view_ledger_entries_cloned() } -} - -impl TypedSecondaryLookupFamily for SemanticQuery { - type SecondaryKey = SemanticBindingLookupKey; - fn matches_secondary(record: &Self::Record, key: &Self::SecondaryKey) -> bool { - record.result.is_ok() - && record.key.input.semantic == key.input - && record.key.imports == key.imports + /// Predecessor source entries compared by the overlay publication's fallback + /// diff; zero whenever the structural-authority path ran (RUE-1112). + pub(crate) fn import_view_source_entries_compared(&self) -> u64 { + self.queries + .revisioned + .import_view_source_entries_compared() } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct DefinitionQueryKey { - input: SemanticInputDescriptor, - imports: CanonicalImportGraph, -} - -#[derive(Debug, Clone)] -struct DefinitionCacheEntry { - key: DefinitionQueryKey, - output: DefinitionQueryOutput, -} -/// Immutable output produced at the stable-definition computation boundary. -/// -/// The provenance is reconstructed from the arguments actually passed to the -/// phase rather than supplied by the cache publisher. This lets the typed store -/// reject publication under a different query key. -#[derive(Debug, Clone)] -struct DefinitionQueryOutput { - provenance: DefinitionQueryKey, - result: Result, CompileErrors>, -} - -#[derive(Debug)] -struct DefinitionComputation { - output: DefinitionQueryOutput, - binding: DeclarationBindingWork, - manifest: SemanticBindingManifestWork, - issuance: BoundDefinitionWork, -} - -#[derive(Debug)] -struct DefinitionQuery; - -impl TypedQueryFamily for DefinitionQuery { - type Key = DefinitionQueryKey; - type Record = DefinitionCacheEntry; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key + /// Predecessor accepted-read entries compared by the overlay publication's + /// provenance diff (RUE-1112). + pub(crate) fn import_view_read_entries_compared(&self) -> u64 { + self.queries.revisioned.import_view_read_entries_compared() } - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.output.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } + /// Structural-sharing witness for the committed import discovery's three + /// additively shared artifacts (RUE-1112): for each of the canonical graph + /// records, the plan's request groups, and the module-resolution table, the + /// identity address of its shared predecessor segment and its delta length. A + /// trusted-toolchain successor carries each predecessor segment `Arc` by + /// reference, so every address equals the predecessor close's — proving no + /// predecessor entry was copied, re-sorted, or reallocated. + pub(crate) fn committed_successor_sharing(&self) -> Option<[(usize, usize); 3]> { + let artifact = self.committed_import_discovery_artifact()?; + let graph = artifact.graph.as_ref()?; + let plan = artifact.plan.as_ref()?; + let record_segments = graph.graph().record_segments(); + let group_segments = plan.group_segments(); + let module_segments = graph.input().resolution.module_segments(); + let witness = + |predecessor_ptr: *const (), delta_len: usize| (predecessor_ptr as usize, delta_len); + Some([ + witness( + Arc::as_ptr(record_segments.predecessor_segment()) as *const (), + record_segments.delta_segment().len(), + ), + witness( + Arc::as_ptr(group_segments.predecessor_segment()) as *const (), + group_segments.delta_segment().len(), + ), + witness( + Arc::as_ptr(module_segments.predecessor_segment()) as *const (), + module_segments.delta_segment().len(), + ), + ]) } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - match (&left.output.result, &right.output.result) { - (Ok(left), Ok(right)) => Arc::ptr_eq(left, right) || left.structurally_eq(right), - (Err(left), Err(right)) => compile_errors_equal(left, right), - _ => false, - } + #[cfg(test)] + pub(crate) fn discovery_attempt(&self) -> Option<&Arc> { + self.discovery_attempt_artifact() } - fn diagnostics_equal(_left: &Self::Record, _right: &Self::Record) -> bool { - true + pub(crate) fn discovery_attempt_artifact( + &self, + ) -> Option<&Arc> { + self.queries + .oracle_import_fault + .as_ref() + .or(self.open_discovery.as_ref()) + .or(self.queries.discovery_attempt.as_ref()) } - - fn record_is_consistent(record: &Self::Record) -> bool { - record.key == record.output.provenance - && record.key.input.sources.root() == record.key.imports.root() - && match &record.output.result { - Ok(definitions) => definitions.source_revision() == &record.key.input.sources, - Err(_) => true, - } + #[cfg(test)] + pub(crate) fn last_good_discovery(&self) -> Option<&Arc> { + self.last_good_discovery_artifact() } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct DependencyManifestQueryKey { - input: SemanticInputDescriptor, - imports: CanonicalImportGraph, -} - -#[derive(Debug)] -struct DependencyManifestQuery; -impl TypedQueryFamily for DependencyManifestQuery { - type Key = DependencyManifestQueryKey; - type Record = DependencyManifestCacheEntry; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key + pub(crate) fn last_good_discovery_artifact( + &self, + ) -> Option<&Arc> { + self.queries.last_good_discovery.as_ref() } - fn terminal_kind(record: &Self::Record) -> TerminalKind { - if record.result.is_ok() { - TerminalKind::Success - } else { - TerminalKind::Failure - } + pub(crate) fn committed_import_discovery_artifact( + &self, + ) -> Option<&Arc> { + let source = self.published.as_ref()?.source_revision(); + self.queries.discovery_attempt.as_ref().filter(|artifact| { + artifact.status == ImportDiscoveryRevisionStatus::ClosedValid + && artifact.source_revision() == source + }) } - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - match (&left.result, &right.result) { - (Ok(left), Ok(right)) => Arc::ptr_eq(left, right) || left == right, - (Err(left), Err(right)) => compile_errors_equal(left, right), - _ => false, - } + /// Return the canonical graph and captured resolution context adopted for + /// the current compiler revision. + pub fn committed_import_graph(&self) -> Result, CompileErrors> { + let committed = self.committed_import_discovery_artifact().ok_or_else(|| { + CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( + "no closed-valid import discovery revision is committed".into(), + ))) + })?; + Ok(committed + .graph() + .expect("closed-valid discovery revisions retain their canonical graph") + .clone()) } - fn diagnostics_equal(_left: &Self::Record, _right: &Self::Record) -> bool { - true + /// Return the sole compiler-owned diagnostic batch for the current import + /// attempt. Batch, emit, and incremental consumers all receive this exact + /// memoized `Arc`. Direct no-I/O sessions use the same query for parser + /// shape preflight; ordinary open discovery work has no publishable batch. + pub fn import_diagnostics(&mut self) -> Result, CompileErrors> { + let mut guard = self.metrics.begin::(); + let mut reused = false; + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.import_diagnostics_attempt(&mut guard, &mut reused) + })); + let result = match result { + Ok(result) => result, + Err(payload) => self.resume_canceled_query(&mut guard, payload), + }; + guard.finish( + if reused { + QueryAttemptExecution::Reused + } else { + QueryAttemptExecution::Computed + }, + None, + &result, + QueryStructuralWork::None, + ); + self.metrics.synchronize(); + result } - fn record_is_consistent(record: &Self::Record) -> bool { - match &record.result { - Ok(manifest) => { - manifest.input == record.key.input && manifest.imports == record.key.imports + fn import_diagnostics_attempt( + &mut self, + guard: &mut QueryComputationGuard, + reused: &mut bool, + ) -> Result, CompileErrors> { + let diagnostics = if let Some(attempt) = self.discovery_attempt_artifact() { + let diagnostics = attempt.diagnostic_snapshot.as_ref().ok_or_else(|| { + CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( + "open import discovery work has no canonical diagnostic batch".into(), + ))) + })?; + *reused = true; + diagnostics.clone() + } else { + let source = self + .published_snapshot + .clone() + .ok_or_else(no_published_program)?; + let input = ImportDiagnosticInputDescriptor { + source: source.source_revision().clone(), + context: None, + plan: None, + ledger: crate::ImportObservationLedger::default(), + accepted_reads: crate::AcceptedReadManifest::from_entries(Vec::new()), + }; + if let Some(diagnostics) = + self.queries + .direct_import_diagnostic + .as_ref() + .filter(|diagnostics| { + diagnostics.source_revision() == source.source_revision() + && diagnostics.identity() + == &FrontendDiagnosticIdentity::Import(input.clone()) + }) + { + *reused = true; + diagnostics.clone() + } else { + let program = self.published.as_ref().ok_or_else(no_published_program)?; + let errors = crate::ImportDiscoveryPlan::shape_diagnostics(program); + guard.started(); + let diagnostics = self.publish_diagnostics( + &source, + FrontendDiagnosticIdentity::Import(input), + Some(&errors), + &[], + ); + self.queries.direct_import_diagnostic = Some(diagnostics.clone()); + diagnostics } - Err(_) => true, - } - } -} - -#[derive(Debug)] -struct InvalidationPlanQuery; - -impl TypedQueryFamily for InvalidationPlanQuery { - type Key = InvalidationPlanQueryKey; - type Record = InvalidationPlanCacheEntry; - const MAX_TERMINALS: usize = FRONTEND_INVALIDATION_PLAN_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(_record: &Self::Record) -> TerminalKind { - TerminalKind::Success - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - Arc::ptr_eq(&left.plan, &right.plan) || left.plan == right.plan + }; + self.diagnostics.select_snapshot(&diagnostics); + self.refresh_retention_metrics(); + Ok(diagnostics) } - fn diagnostics_equal(_left: &Self::Record, _right: &Self::Record) -> bool { - true + fn require_successful_import_diagnostics(&mut self) -> Result<(), CompileErrors> { + let diagnostics = self.import_diagnostics()?; + if diagnostics.errors().is_empty() { + Ok(()) + } else { + Err(CompileErrors::from(diagnostics.errors().to_vec())) + } } - fn record_is_consistent(_record: &Self::Record) -> bool { - true + #[cfg(test)] + pub(crate) fn stage_import_discovery( + &mut self, + snapshot: &SourceSnapshot, + context: crate::ImportDiscoveryContext, + accepted_reads: Arc<[crate::AcceptedReadManifestEntry]>, + carried_ledger: crate::ImportObservationLedger, + ) -> Result { + self.stage_import_discovery_inner( + snapshot, + context, + crate::AcceptedReadManifest::from_shared(accepted_reads), + carried_ledger, + None, + ) } -} - -macro_rules! session_query_metrics_family { - ($query:ty, $name:literal, $field:ident) => { - impl SessionQueryMetricsFamily for $query { - const NAME: &'static str = $name; - fn projection(work: &mut CompilerSessionWork) -> &mut FrontendQueryWork { - &mut work.$field - } + /// Verify a [`TrustedSuccessorDelta`] against this session and the CURRENT + /// compiler-published import-input view, returning that view's exact state + /// together with the derived module delta (RUE-1112). The successor stage and + /// close consume ONLY this published state — snapshot, context, provenance, + /// and ledger — so a caller cannot substitute any replacement; the view + /// itself is only extendable through justified overlay publications, so the + /// derived delta always equals the accumulated compiler-authorized additions. + fn derive_successor_state( + &self, + delta: &TrustedSuccessorDelta, + ) -> Result { + let reject = |message: &str| { + CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( + format!("trusted-toolchain successor delta rejected: {message}"), + ))) + }; + if !Arc::ptr_eq(&delta.session, &self.identity) { + return Err(reject("the successor delta belongs to a different session")); } - }; -} - -session_query_metrics_family!(ImportsMetricsQuery, "imports", imports); -session_query_metrics_family!( - ImportDiagnosticQuery, - "import-diagnostics", - import_diagnostics -); -session_query_metrics_family!(MergeQuery, "merge", merge); -session_query_metrics_family!(RirQuery, "rir", rir); -session_query_metrics_family!(SemanticQuery, "semantic", semantic); -session_query_metrics_family!(DefinitionQuery, "definitions", definitions); -session_query_metrics_family!( - DependencyManifestQuery, - "dependency-manifests", - dependency_manifests -); -session_query_metrics_family!( - InvalidationPlanQuery, - "invalidation-plans", - invalidation_plans -); - -/// Explicit compiler inputs read by a terminal attempt. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct ExactSourceInput { - revision: SourceRevision, - metadata: crate::SourceMetadata, -} - -impl ExactSourceInput { - pub(crate) fn new(snapshot: &SourceSnapshot) -> Self { - Self { - revision: snapshot.source_revision().clone(), - metadata: snapshot.metadata().clone(), + let Some(outstanding) = self.successor_delta_nonce else { + return Err(reject( + "no outstanding successor-delta authority; it was already consumed or invalidated", + )); + }; + if outstanding != delta.nonce { + return Err(reject( + "the successor delta is stale (superseded by a newer publish, request, or close)", + )); } - } -} - -fn compute_stable_definitions( - merged: &CanonicalMergedProgram, - options: &CompileOptions, - imports: &CanonicalImportGraph, - semantic: &CanonicalSemanticOutput, -) -> DefinitionComputation { - let provenance = DefinitionQueryKey { - input: SemanticInputDescriptor::new( - merged.definitions().source_snapshot(), - options.target, - &options.preview_features, - ), - imports: imports.clone(), - }; - // A semantic terminal normally belongs to this exact source descriptor. - // Re-stamp the projection at this query boundary so typed fault injection - // remains a detectable stale value rather than publishing an internally - // inconsistent definition terminal. - let definitions = semantic - .body_owner_issuer() - .projected_for_source_revision(merged.ast().source_revision()); - let work = semantic.work(); - let binding = work.binding; - let manifest = work.manifest; - let issuance = definitions.work(); - let result = Ok(Arc::new(definitions)); - DefinitionComputation { - output: DefinitionQueryOutput { provenance, result }, - binding, - manifest, - issuance, - } -} - -impl CompilerSession { - #[cfg(test)] - pub(crate) fn with_query_concurrency(workers: usize) -> Self { - let mut session = Self::default(); - session.queries.revisioned = - crate::revisioned_query_database::RevisionedQueryDatabase::with_query_concurrency( - workers, - ); - session - } - /// Corrupt actual retained/selectable query state for the differential - /// oracle. This is deliberately typed and narrow; production callers have - /// no reason to use it. - #[doc(hidden)] - pub(crate) fn inject_stale_query_for_oracle( - &mut self, - fault: crate::unstable::DifferentialOracleFault, - ) -> bool { - match fault { - crate::unstable::DifferentialOracleFault::Semantic => { - let records = self.queries.semantic.records().cloned().collect::>(); - let Some(current) = records.last().cloned() else { - return false; - }; - let Some(stale) = records - .iter() - .rev() - .skip(1) - .find(|record| record.result.is_ok() && record.key != current.key) - else { - return false; - }; - let mut injected = current; - injected.result = stale.result.clone(); - injected.rir = stale.rir.clone(); - injected.durable_declaration_cache = stale.durable_declaration_cache.clone(); - injected.oracle_injected = true; - self.queries.semantic.insert_with_dependencies( - &mut self.queries.graph, - injected, - [], - ); - true - } - crate::unstable::DifferentialOracleFault::Diagnostic => { - let Some(latest) = self.diagnostics.latest().cloned() else { - return false; - }; - let stale = self - .queries - .semantic - .records() - .map(|record| record.diagnostics.clone()) - .find(|diagnostics| { - !Arc::ptr_eq(diagnostics, &latest) - && !diagnostic_batches_equal(diagnostics, &latest) - }); - let Some(stale) = stale else { - return false; - }; - self.diagnostics.select_snapshot(&stale); - true - } - crate::unstable::DifferentialOracleFault::Import => { - let Some(current) = self - .queries - .import_closures - .selected_record(&self.queries.graph) - .cloned() - else { - return false; - }; - let stale = self - .queries - .import_closures - .records() - .find(|record| { - record.key != current.key - && record.artifact.source_revision() - != current.artifact.source_revision() - }) - .map(|record| record.key.clone()); - let Some(stale) = stale else { - return false; - }; - self.queries - .import_closures - .select(&mut self.queries.graph, stale); - true - } + let Some((current, snapshot, context, accepted_reads, ledger)) = + self.queries.revisioned.current_import_view_state() + else { + return Err(reject( + "no current import-input view backs the successor delta", + )); + }; + if current.request_generation != delta.revision.request_generation { + return Err(reject( + "the successor delta belongs to a different request generation than the current view", + )); } - } - - pub fn new() -> Self { - Self::default() - } - - fn resume_canceled_query( - &mut self, - guard: &mut QueryComputationGuard, - payload: Box, - ) -> ! { - let reason = if let Some(reason) = payload - .downcast_ref::() - .copied() - { - match reason { - crate::typed_query_store::BeginSelectedError::DuplicateInFlight => { - AbortedQueryReason::DuplicateInFlight - } - crate::typed_query_store::BeginSelectedError::DependencyCycle => { - AbortedQueryReason::DependencyCycle - } - crate::typed_query_store::BeginSelectedError::KeyNotSelected - | crate::typed_query_store::BeginSelectedError::AlreadyTerminal => { - AbortedQueryReason::Canceled - } + // The module delta is the session-owned recorded-additions lineage: the + // exact additions each overlay publication recorded since the committed + // close. Predecessor byte-identity is enforced where state changes — at + // every overlay publication — so nothing is re-derived by scanning + // complete views here; this read is O(delta). + let mut new_modules: Vec = + self.queries.revisioned.lineage_additions().to_vec(); + new_modules.sort_by(|a, b| a.module.cmp(&b.module)); + new_modules.dedup(); + // Every authorized appended module must be present, so the successor can + // never omit a demanded trusted module. + let new_set: std::collections::BTreeSet<&crate::ModuleId> = new_modules + .iter() + .map(|revision| &revision.module) + .collect(); + for module in delta.appended.iter() { + if !new_set.contains(module) { + return Err(reject( + "the successor omits an authorized appended module; it must contain every demanded trusted module", + )); } - } else { - AbortedQueryReason::Canceled - }; - let work = guard.structural(); - let dependencies = guard.dependencies.clone(); - let diagnostics = guard.diagnostics.clone(); - let attempt = match guard.family { - "import-diagnostics" => self.queries.import_diagnostics.computing_key().map(|key| { - self.queries.import_diagnostics.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work.clone(), - dependencies.clone(), - diagnostics.clone(), - ) - }), - "merge" => self.queries.merge.computing_key().map(|key| { - self.queries.merge.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work.clone(), - dependencies.clone(), - diagnostics.clone(), - ) - }), - "rir" => self.queries.rir.computing_key().map(|key| { - self.queries.rir.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work.clone(), - dependencies.clone(), - diagnostics.clone(), - ) - }), - "semantic" => self.queries.semantic.computing_key().map(|key| { - self.queries.semantic.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work.clone(), - dependencies.clone(), - diagnostics.clone(), - ) - }), - "definitions" => self.queries.definitions.computing_key().map(|key| { - self.queries.definitions.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work.clone(), - dependencies.clone(), - diagnostics.clone(), - ) - }), - "dependency-manifests" => self.queries.manifests.computing_key().map(|key| { - self.queries.manifests.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work.clone(), - dependencies.clone(), - diagnostics.clone(), - ) - }), - "invalidation-plans" => self.queries.invalidation_plans.computing_key().map(|key| { - self.queries.invalidation_plans.record_aborted_attempt( - &mut self.queries.graph, - key, - guard.id.0, - reason, - work, - dependencies, - diagnostics, - ) - }), - "imports" | "parse" => None, - family => unreachable!("unknown query guard family {family}"), - }; - if let Some(attempt) = attempt { - guard.bind(attempt); } - self.metrics.synchronize(); - std::panic::resume_unwind(payload) + Ok(SuccessorState { + snapshot, + context, + accepted_reads, + ledger, + revision: current, + delta: new_modules.into(), + }) } - fn cancel_merge_at_commit_boundary(&mut self) -> bool { - #[cfg(test)] - return std::mem::take(&mut self.cancel_merge_before_commit); - #[cfg(not(test))] - false + /// Stage a strictly-additive trusted-toolchain successor (RUE-1112). The + /// staged snapshot, context, provenance, and carried ledger are the CURRENT + /// compiler-published view's own state, and the module delta is derived and + /// verified from the opaque `delta` capability — the caller supplies nothing + /// but the capability. The plan reuses the committed predecessor plan's + /// request groups and constructs groups only for the delta's import + /// occurrences, so predecessor occurrences are never re-staged. + pub(crate) fn stage_import_discovery_successor( + &mut self, + delta: &TrustedSuccessorDelta, + ) -> Result { + let state = self.derive_successor_state(delta)?; + self.stage_import_discovery_inner( + &state.snapshot, + state.context, + state.accepted_reads, + state.ledger, + Some((state.revision, state.delta)), + ) } - /// Select the accepted import topology for semantic construction. - /// - /// Import-bearing revisions must come from the atomically adopted - /// discovery artifact. A direct session remains usable for an import-free - /// snapshot by supplying the uniquely valid empty graph; it may never - /// reconstruct resolved imports from paths or environment state. - fn accepted_semantic_import_graph(&self) -> Result { - let program = self.published.as_ref().ok_or_else(no_published_program)?; - let graph = if !program.import_directives().is_empty() { - let committed = self.committed_import_graph()?; - if &committed.input().sources != program.source_revision() { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "committed import graph belongs to a foreign source revision".into(), - ), - ))); - } - committed.graph().clone() - } else { - crate::import_graph::import_free_canonical_graph(program.as_ref())? + fn stage_import_discovery_inner( + &mut self, + snapshot: &SourceSnapshot, + context: crate::ImportDiscoveryContext, + accepted_reads: crate::AcceptedReadManifest, + carried_ledger: crate::ImportObservationLedger, + successor: Option<(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, + ) -> Result { + let new_module_ids: Option> = successor.as_ref().map(|(_, delta)| { + delta + .iter() + .map(|revision| revision.module.clone()) + .collect() + }); + let new_modules: Option<&[crate::ModuleId]> = new_module_ids.as_deref(); + let continuation = self.open_discovery.as_deref().filter(|attempt| { + continues_discovery_lifecycle( + attempt, + snapshot, + &context, + &accepted_reads, + &carried_ledger, + ) + }); + let mut parse_work = + continuation.map_or_else(ParsedModulesWork::default, |attempt| attempt.parse_work); + // Reinstall protocol context only if staging reaches Open. Closed + // attempts are retained as projections of the canonical frontier. + self.open_discovery = None; + let source_revision = snapshot.source_revision().clone(); + if let Err(errors) = validate_accepted_read_manifest(snapshot, &accepted_reads) { + let diagnostic_snapshot = self.publish_import_diagnostics( + snapshot, + Some(context.clone()), + None, + carried_ledger.clone(), + accepted_reads.clone(), + &errors, + ); + let attempted_artifact = Arc::new(ImportDiscoveryRevisionArtifact { + status: ImportDiscoveryRevisionStatus::ClosedAttempted, + source_revision: source_revision.clone(), + context: context.clone(), + snapshot: snapshot.clone(), + program: None, + parse_work, + plan: None, + ledger: carried_ledger.clone(), + accepted_reads: accepted_reads.clone(), + graph: None, + diagnostics: errors.clone(), + diagnostic_snapshot: Some(diagnostic_snapshot), + successor_parse: None, + }); + self.queries.record_discovery_attempt(attempted_artifact); + return Err(errors); + } + // Staging splits into the canonical parse of everything read so far and + // the import-plan construction over the resulting program. Both were + // previously folded into the driver's unattributed region (RUE-786). + let parse_staging_span = tracing::info_span!("import_parse_staging").entered(); + let (parse_result, staged_work, staged_successor_parse) = self.parse_staging_snapshot( + snapshot, + successor + .as_ref() + .map(|(revision, delta)| (*revision, delta)), + ); + drop(parse_staging_span); + parse_work.accumulate(staged_work); + let program = match parse_result { + Ok(program) => program, + Err(errors) => { + let diagnostic_snapshot = self.publish_import_diagnostics( + snapshot, + Some(context.clone()), + None, + carried_ledger.clone(), + accepted_reads.clone(), + &errors, + ); + let attempted_artifact = Arc::new(ImportDiscoveryRevisionArtifact { + status: ImportDiscoveryRevisionStatus::ClosedAttempted, + source_revision: source_revision.clone(), + context: context.clone(), + snapshot: snapshot.clone(), + program: None, + parse_work, + plan: None, + ledger: carried_ledger.clone(), + accepted_reads: accepted_reads.clone(), + graph: None, + diagnostics: errors.clone(), + diagnostic_snapshot: Some(diagnostic_snapshot), + successor_parse: None, + }); + self.queries.record_discovery_attempt(attempted_artifact); + return Err(errors); + } }; - Ok(graph) - } - pub fn published(&self) -> Option { - self.published.as_ref().cloned().map(crate::SyntaxView::new) + // A trusted-toolchain successor stage reuses the committed predecessor + // plan's request groups and constructs groups only for the newly appended + // modules' occurrences; predecessor occurrences are never re-staged. When + // no predecessor plan is retained (an unexpected legacy state) it falls + // back to a full build so the plan is always complete. + let predecessor_plan = new_modules.and_then(|_| { + self.last_good_discovery_artifact() + .and_then(|artifact| artifact.plan.clone()) + }); + let plan_build_span = tracing::info_span!("import_plan_build").entered(); + let plan_build = match (new_modules, predecessor_plan) { + (Some(new_modules), Some(predecessor)) => { + crate::ImportDiscoveryPlan::extend_trusted_successor( + &predecessor, + &program, + context.clone(), + new_modules, + ) + .map(|(plan, constructed)| { + self.import_plan_groups_constructed = self + .import_plan_groups_constructed + .saturating_add(constructed); + plan + }) + } + _ => crate::ImportDiscoveryPlan::new(&program, context.clone()).inspect(|plan| { + self.import_plan_groups_constructed = self + .import_plan_groups_constructed + .saturating_add(plan.groups().len() as u64); + }), + }; + let plan = match plan_build { + Ok(plan) => plan, + Err(error) => { + let errors = CompileErrors::from(error); + let diagnostic_snapshot = self.publish_import_diagnostics( + snapshot, + Some(context.clone()), + None, + carried_ledger.clone(), + accepted_reads.clone(), + &errors, + ); + let attempted_artifact = Arc::new(ImportDiscoveryRevisionArtifact { + status: ImportDiscoveryRevisionStatus::ClosedAttempted, + source_revision: source_revision.clone(), + context: context.clone(), + snapshot: snapshot.clone(), + program: Some(program), + parse_work, + plan: None, + ledger: carried_ledger.clone(), + accepted_reads: accepted_reads.clone(), + graph: None, + diagnostics: errors.clone(), + diagnostic_snapshot: Some(diagnostic_snapshot), + successor_parse: None, + }); + self.queries.record_discovery_attempt(attempted_artifact); + return Err(errors); + } + }; + drop(plan_build_span); + let _plan_publish_span = tracing::info_span!("import_plan_publish").entered(); + let shape_diagnostics = crate::ImportDiscoveryPlan::shape_diagnostics(&program); + self.publish_import_diagnostics( + snapshot, + Some(context.clone()), + Some(plan.clone()), + carried_ledger.clone(), + accepted_reads.clone(), + &shape_diagnostics, + ); + self.open_discovery = Some(Arc::new(ImportDiscoveryRevisionArtifact { + status: ImportDiscoveryRevisionStatus::Open, + source_revision: program.source_revision().clone(), + context, + snapshot: snapshot.clone(), + program: Some(program), + parse_work, + plan: Some(plan.clone()), + ledger: carried_ledger, + accepted_reads, + graph: None, + diagnostics: CompileErrors::new(), + diagnostic_snapshot: None, + successor_parse: staged_successor_parse, + })); + Ok(plan) } - pub(crate) fn published_owner(&self) -> Option<&Arc> { - self.published.as_ref() + #[cfg(test)] + pub(crate) fn close_import_discovery( + &mut self, + ledger: crate::ImportObservationLedger, + ) -> Result, CompileErrors> { + self.close_import_discovery_artifact(ledger, None) } - /// Derive the pre-closure import plan for the session's current parsed - /// revision. + /// Closes the current compiler-published import-input revision. /// - /// This is retained only as part of the RUE-1033 legacy supported import - /// path. It is not freshness- or speculation-safe, and new consumers must - /// use the unstable begin/frontier/publish protocol. - pub fn import_discovery_plan( - &self, - context: crate::ImportDiscoveryContext, - ) -> crate::CompileResult { - let program = self.published.as_ref().ok_or_else(|| { - CompileError::without_span(ErrorKind::InvalidCompilerInput( - "import discovery requires a successfully parsed staging revision".into(), - )) - })?; - crate::ImportDiscoveryPlan::new(program, context) + /// The closing ledger comes from the same immutable revision that supplied + /// the staged plan. This keeps root discovery authority in the compiler. + pub(crate) fn close_import_input_request( + &mut self, + revision: crate::ImportInputRevision, + ) -> Result, CompileErrors> { + let Some((current, _, _, _, ledger)) = self.queries.revisioned.current_import_view_state() + else { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "import closure requires a current compiler-published input revision".into(), + ), + ))); + }; + if current != revision { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "import closure requested a non-current compiler-published input revision" + .into(), + ), + ))); + } + self.close_import_discovery_artifact(ledger, None) + .map(|artifact| Arc::new(crate::ImportDiscoveryView::new(artifact))) } - /// Begins one fresh rooted external-input request with granular immutable - /// module source, accepted-read provenance, and observation leaves. - /// Successor carry is available only through batch publication. - pub(crate) fn begin_import_input_request( + /// Close a strictly-additive trusted-toolchain successor (RUE-1112). The + /// closing ledger is the CURRENT compiler-published view's own carried + /// ledger and the module delta is derived from the opaque capability — the + /// caller supplies nothing but the capability, so no replacement ledger or + /// module set can be substituted. The close projects and reduces only the + /// delta occurrences and merges them into the committed predecessor's closed + /// graph, never re-projecting or re-reducing predecessor occurrences. + pub(crate) fn close_import_discovery_successor( &mut self, - snapshot: &SourceSnapshot, - context: crate::ImportDiscoveryContext, - accepted_reads: crate::AcceptedReadManifest, - ) -> crate::CompileResult { - // A fresh observation generation invalidates any outstanding - // trusted-toolchain continuation and successor-delta authority (RUE-1112). - self.continuation = None; + delta: &TrustedSuccessorDelta, + ) -> Result, CompileErrors> { + let state = self.derive_successor_state(delta)?; + let closed = self + .close_import_discovery_artifact(state.ledger, Some((state.revision, state.delta)))?; + // Consume the single-use delta authority only on a successful close. self.successor_delta_nonce = None; - self.queries - .revisioned - .begin_import_inputs(snapshot, context, accepted_reads) + Ok(closed) } - pub(crate) fn import_demand_frontier_for_roots( + fn close_import_discovery_artifact( &mut self, - revision: crate::ImportInputRevision, - plan: &crate::ImportDiscoveryPlan, - mode: crate::ImportDemandMode, - roots: &crate::ImportDemandRoots, - ) -> crate::CompileResult { - self.queries - .revisioned - .import_frontier(revision, plan, mode, roots) - } - - /// Publishes exactly one compiler-produced rooted host batch as one - /// successor immutable revision. - pub(crate) fn publish_import_observation_batch( - &mut self, - frontier: &crate::ImportDemandFrontier, - snapshot: &SourceSnapshot, - accepted_reads: crate::AcceptedReadManifest, - observations: Vec, - ) -> crate::CompileResult { - self.queries.revisioned.publish_import_batch( - frontier, - snapshot, - accepted_reads, - observations, - ) - } - - /// Returns the immutable canonical ledger carried by one input revision. - pub(crate) fn import_observation_ledger( - &self, - revision: crate::ImportInputRevision, - ) -> crate::CompileResult { - self.queries.revisioned.import_ledger(revision) - } - - /// Cumulative import occurrences the demand frontier has rooted (RUE-1112). - /// One `ResolveImport` projection is dispatched per rooted occurrence, so the - /// delta across a trusted-toolchain re-close counts only the newly appended - /// leaves and modules newly discovered from them — never a predecessor - /// occurrence. The host driver reads this to prove the re-close does not - /// re-root the predecessor import topology. - pub(crate) fn import_frontier_roots_requested(&self) -> u64 { - self.queries.revisioned.import_frontier_roots_requested() - } - - /// Cumulative import-plan request groups constructed during staging - /// (RUE-1112). See the field docs on `import_plan_groups_constructed`. - pub(crate) fn import_plan_groups_constructed(&self) -> u64 { - self.import_plan_groups_constructed - } - - /// See the field docs on `parse_sources_materialized`. - pub(crate) fn parse_sources_materialized(&self) -> u64 { - self.parse_sources_materialized - } - - /// See the field docs on `parse_key_entries_compared`. - pub(crate) fn parse_key_entries_compared(&self) -> u64 { - self.parse_key_entries_compared - } - - /// See the field docs on `parse_modules_dispatched`. - pub(crate) fn parse_modules_dispatched(&self) -> u64 { - self.parse_modules_dispatched - } - - /// See the field docs on `parse_invalidation_entries_compared`. - pub(crate) fn parse_invalidation_entries_compared(&self) -> u64 { - self.parse_invalidation_entries_compared - } - - /// A snapshot of the production provider-op observation counters - /// (ADR-0066 §4). - pub(crate) fn provider_observation_metrics( - &self, - ) -> crate::unstable::ProviderObservationMetrics { - self.queries.revisioned.provider_observation_metrics() - } - - /// A snapshot of the lookup-family pressure metrics (RUE-1091, ADR-0066 §4). - /// Production body publications retain their exact observed lookup - /// terminals in the session's `PublishedRootLookupLease`. - pub(crate) fn lookup_pressure_metrics(&self) -> crate::unstable::LookupPressureMetrics { - self.queries.revisioned.lookup_pressure_metrics() - } - - /// The currently selected parse terminal, for identity assertions. - #[cfg(test)] - pub(crate) fn selected_parse_terminal( - &self, - ) -> Option>> { - self.queries.revisioned.parse.selected_terminal() - } - - /// Cumulative dependency-graph invalidation events across the retained - /// frontend query families (merge, import diagnostics, RIR, semantic, - /// definitions, dependency manifests). A strictly-additive successor - /// adoption keeps the predecessor's immutable source leaf live, so it - /// contributes ZERO here regardless of how many variants are retained; - /// only a genuine replacement (an ordinary update) invalidates dependents. - pub(crate) fn frontend_query_invalidations(&self) -> u64 { - let graph = &self.queries.graph; - (graph.invalidation_count::() - + graph.invalidation_count::() - + graph.invalidation_count::() - + graph.invalidation_count::() - + graph.invalidation_count::() - + graph.invalidation_count::()) as u64 - } - - /// Cumulative close-time `ResolveImport` projections dispatched (RUE-1112). - pub(crate) fn exact_import_groups_dispatched(&self) -> u64 { - self.queries.revisioned.exact_import_groups_dispatched() - } - - /// Cumulative canonical import records reduced and validated during close - /// (RUE-1112). See the field docs on `import_close_records_reduced`. - pub(crate) fn import_close_records_reduced(&self) -> u64 { - self.import_close_records_reduced - } - - /// Cumulative leaves published through the complete publication path - /// (fresh generations); scales with the program (RUE-1112). - pub(crate) fn import_view_full_leaves_published(&self) -> u64 { - self.queries.revisioned.import_view_full_leaves_published() - } - - /// Cumulative delta leaves published through the sparse successor overlay - /// path; predecessor leaves are structurally inherited and never counted - /// (RUE-1112). - pub(crate) fn import_view_overlay_leaves_published(&self) -> u64 { - self.queries - .revisioned - .import_view_overlay_leaves_published() - } - - /// Cumulative predecessor ledger observations deep-cloned into successor - /// view ledgers (visible remaining cost; RUE-1112). - pub(crate) fn import_view_ledger_entries_cloned(&self) -> u64 { - self.queries.revisioned.import_view_ledger_entries_cloned() - } - - /// Predecessor source entries compared by the overlay publication's fallback - /// diff; zero whenever the structural-authority path ran (RUE-1112). - pub(crate) fn import_view_source_entries_compared(&self) -> u64 { - self.queries - .revisioned - .import_view_source_entries_compared() - } - - /// Predecessor accepted-read entries compared by the overlay publication's - /// provenance diff (RUE-1112). - pub(crate) fn import_view_read_entries_compared(&self) -> u64 { - self.queries.revisioned.import_view_read_entries_compared() - } - - /// Structural-sharing witness for the committed import discovery's three - /// additively shared artifacts (RUE-1112): for each of the canonical graph - /// records, the plan's request groups, and the module-resolution table, the - /// identity address of its shared predecessor segment and its delta length. A - /// trusted-toolchain successor carries each predecessor segment `Arc` by - /// reference, so every address equals the predecessor close's — proving no - /// predecessor entry was copied, re-sorted, or reallocated. - pub(crate) fn committed_successor_sharing(&self) -> Option<[(usize, usize); 3]> { - let artifact = self.committed_import_discovery_artifact()?; - let graph = artifact.graph.as_ref()?; - let plan = artifact.plan.as_ref()?; - let record_segments = graph.graph().record_segments(); - let group_segments = plan.group_segments(); - let module_segments = graph.input().resolution.module_segments(); - let witness = - |predecessor_ptr: *const (), delta_len: usize| (predecessor_ptr as usize, delta_len); - Some([ - witness( - Arc::as_ptr(record_segments.predecessor_segment()) as *const (), - record_segments.delta_segment().len(), - ), - witness( - Arc::as_ptr(group_segments.predecessor_segment()) as *const (), - group_segments.delta_segment().len(), - ), - witness( - Arc::as_ptr(module_segments.predecessor_segment()) as *const (), - module_segments.delta_segment().len(), - ), - ]) - } - #[cfg(test)] - pub(crate) fn discovery_attempt(&self) -> Option<&Arc> { - self.discovery_attempt_artifact() - } - - pub(crate) fn discovery_attempt_artifact( - &self, - ) -> Option<&Arc> { - self.open_discovery - .as_ref() - .or_else(|| { - self.queries - .import_plans - .selected_record(&self.queries.graph) - .and_then(|record| record.attempted_artifact.as_ref()) - }) - .or_else(|| { - self.queries - .import_closures - .selected_record(&self.queries.graph) - .map(|record| &record.artifact) - }) - } - #[cfg(test)] - pub(crate) fn last_good_discovery(&self) -> Option<&Arc> { - self.last_good_discovery_artifact() - } - - pub(crate) fn last_good_discovery_artifact( - &self, - ) -> Option<&Arc> { - self.queries - .import_closures - .last_good_record() - .map(|record| &record.artifact) - } - - pub(crate) fn committed_import_discovery_artifact( - &self, - ) -> Option<&Arc> { - let source = self.published.as_ref()?.source_revision(); - self.queries - .import_closures - .selected_record(&self.queries.graph) - .filter(|record| record.result.is_ok() && record.artifact.source_revision() == source) - .map(|record| &record.artifact) - } - - /// Return the canonical graph and captured resolution context adopted for - /// the current compiler revision. - pub fn committed_import_graph(&self) -> Result, CompileErrors> { - let committed = self.committed_import_discovery_artifact().ok_or_else(|| { - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - "no closed-valid import discovery revision is committed".into(), - ))) - })?; - Ok(committed - .graph() - .expect("closed-valid discovery revisions retain their canonical graph") - .clone()) - } - - /// Return the sole compiler-owned diagnostic batch for the current import - /// attempt. Batch, emit, and incremental consumers all receive this exact - /// memoized `Arc`. Direct no-I/O sessions use the same query for parser - /// shape preflight; ordinary open discovery work has no publishable batch. - pub fn import_diagnostics(&mut self) -> Result, CompileErrors> { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let mut execution = QueryAttemptExecution::Rejected; - let mut origin = None; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.import_diagnostics_attempt(attempt_id, &mut guard, &mut execution, &mut origin) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - if execution == QueryAttemptExecution::Reused && origin.is_none() { - execution = QueryAttemptExecution::Adopted; - } - guard.finish(execution, origin, &result, QueryStructuralWork::None); - self.metrics.synchronize(); - result - } - - fn import_diagnostics_attempt( - &mut self, - attempt_id: AttemptId, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - origin: &mut Option, - ) -> Result, CompileErrors> { - let diagnostics = if let Some(attempt) = self.discovery_attempt_artifact() { - let diagnostics = attempt.diagnostic_snapshot.as_ref().ok_or_else(|| { - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - "open import discovery work has no canonical diagnostic batch".into(), - ))) - })?; - *execution = QueryAttemptExecution::Reused; - diagnostics.clone() - } else { - let source = self - .published_snapshot - .clone() - .ok_or_else(no_published_program)?; - let input = ImportDiagnosticInputDescriptor { - source: source.source_revision().clone(), - context: None, - plan: None, - ledger: crate::ImportObservationLedger::default(), - accepted_reads: crate::AcceptedReadManifest::from_entries(Vec::new()), - }; - if let Some((cached, handle)) = self - .queries - .import_diagnostics - .request_selected(&mut self.queries.graph, input.clone(), attempt_id.0) - .unwrap_or_else(query_control_error) - { - *execution = QueryAttemptExecution::Reused; - *origin = Some(handle.origin_attempt_id()); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - cached.diagnostics.clone() - } else { - let program = self.published.as_ref().ok_or_else(no_published_program)?; - let errors = crate::ImportDiscoveryPlan::shape_diagnostics(program); - *execution = QueryAttemptExecution::Computed; - guard.started(); - let diagnostics = self.publish_diagnostics( - &source, - FrontendDiagnosticIdentity::Import(input.clone()), - Some(&errors), - &[], - ); - let source_dependency = self - .queries - .source_inputs - .selected(&self.queries.graph) - .expect("import diagnostics retain their exact source input"); - let handle = self - .queries - .import_diagnostics - .publish_selected( - &mut self.queries.graph, - DirectImportDiagnosticCacheEntry { - key: input, - diagnostics: diagnostics.clone(), - }, - [source_dependency], - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - diagnostics - } - }; - self.diagnostics.select_snapshot(&diagnostics); - self.refresh_retention_metrics(); - Ok(diagnostics) - } - - fn require_successful_import_diagnostics(&mut self) -> Result<(), CompileErrors> { - let diagnostics = self.import_diagnostics()?; - if diagnostics.errors().is_empty() { - Ok(()) - } else { - Err(CompileErrors::from(diagnostics.errors().to_vec())) - } - } - - /// Parse an immutable staging snapshot without publishing it to semantic - /// or dependency queries. - /// - /// The carried ledger and this operation are retained only for the - /// RUE-1033 legacy compatibility boundary. They are not freshness- or - /// speculation-safe; new consumers must use the unstable canonical - /// begin/frontier/publish protocol. - pub fn stage_import_discovery( - &mut self, - snapshot: &SourceSnapshot, - context: crate::ImportDiscoveryContext, - accepted_reads: Arc<[crate::AcceptedReadManifestEntry]>, - carried_ledger: crate::ImportObservationLedger, - ) -> Result { - self.stage_import_discovery_inner( - snapshot, - context, - crate::AcceptedReadManifest::from_shared(accepted_reads), - carried_ledger, - None, - ) - } - - /// Verify a [`TrustedSuccessorDelta`] against this session and the CURRENT - /// compiler-published import-input view, returning that view's exact state - /// together with the derived module delta (RUE-1112). The successor stage and - /// close consume ONLY this published state — snapshot, context, provenance, - /// and ledger — so a caller cannot substitute any replacement; the view - /// itself is only extendable through justified overlay publications, so the - /// derived delta always equals the accumulated compiler-authorized additions. - fn derive_successor_state( - &self, - delta: &TrustedSuccessorDelta, - ) -> Result { - let reject = |message: &str| { - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - format!("trusted-toolchain successor delta rejected: {message}"), - ))) - }; - if !Arc::ptr_eq(&delta.session, &self.identity) { - return Err(reject("the successor delta belongs to a different session")); - } - let Some(outstanding) = self.successor_delta_nonce else { - return Err(reject( - "no outstanding successor-delta authority; it was already consumed or invalidated", - )); - }; - if outstanding != delta.nonce { - return Err(reject( - "the successor delta is stale (superseded by a newer publish, request, or close)", - )); - } - let Some((current, snapshot, context, accepted_reads, ledger)) = - self.queries.revisioned.current_import_view_state() - else { - return Err(reject( - "no current import-input view backs the successor delta", - )); - }; - if current.request_generation != delta.revision.request_generation { - return Err(reject( - "the successor delta belongs to a different request generation than the current view", - )); - } - // The module delta is the session-owned recorded-additions lineage: the - // exact additions each overlay publication recorded since the committed - // close. Predecessor byte-identity is enforced where state changes — at - // every overlay publication — so nothing is re-derived by scanning - // complete views here; this read is O(delta). - let mut new_modules: Vec = - self.queries.revisioned.lineage_additions().to_vec(); - new_modules.sort_by(|a, b| a.module.cmp(&b.module)); - new_modules.dedup(); - // Every authorized appended module must be present, so the successor can - // never omit a demanded trusted module. - let new_set: std::collections::BTreeSet<&crate::ModuleId> = new_modules - .iter() - .map(|revision| &revision.module) - .collect(); - for module in delta.appended.iter() { - if !new_set.contains(module) { - return Err(reject( - "the successor omits an authorized appended module; it must contain every demanded trusted module", - )); - } - } - Ok(SuccessorState { - snapshot, - context, - accepted_reads, - ledger, - revision: current, - delta: new_modules.into(), - }) - } - - /// Stage a strictly-additive trusted-toolchain successor (RUE-1112). The - /// staged snapshot, context, provenance, and carried ledger are the CURRENT - /// compiler-published view's own state, and the module delta is derived and - /// verified from the opaque `delta` capability — the caller supplies nothing - /// but the capability. The plan reuses the committed predecessor plan's - /// request groups and constructs groups only for the delta's import - /// occurrences, so predecessor occurrences are never re-staged. - pub(crate) fn stage_import_discovery_successor( - &mut self, - delta: &TrustedSuccessorDelta, - ) -> Result { - let state = self.derive_successor_state(delta)?; - self.stage_import_discovery_inner( - &state.snapshot, - state.context, - state.accepted_reads, - state.ledger, - Some((state.revision, state.delta)), - ) - } - - fn stage_import_discovery_inner( - &mut self, - snapshot: &SourceSnapshot, - context: crate::ImportDiscoveryContext, - accepted_reads: crate::AcceptedReadManifest, - carried_ledger: crate::ImportObservationLedger, - successor: Option<(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, - ) -> Result { - let new_module_ids: Option> = successor.as_ref().map(|(_, delta)| { - delta - .iter() - .map(|revision| revision.module.clone()) - .collect() - }); - let new_modules: Option<&[crate::ModuleId]> = new_module_ids.as_deref(); - let continuation = self.open_discovery.as_deref().filter(|attempt| { - continues_discovery_lifecycle( - attempt, - snapshot, - &context, - &accepted_reads, - &carried_ledger, - ) - }); - let mut parse_work = - continuation.map_or_else(ParsedModulesWork::default, |attempt| attempt.parse_work); - // From this point the selected typed plan terminal owns any closed - // failure. Reinstall protocol context only if staging reaches Open. - self.open_discovery = None; - let source_revision = snapshot.source_revision().clone(); - // A successor stage keys on {published lineage identity, exact delta} - // rather than re-hashing the full content; the ordinary path keeps its - // exact content key and therefore its warm reuse. - let plan_key = match &successor { - Some((revision, delta)) => ImportPlanQueryKey::Successor { - revision: *revision, - delta: delta.clone(), - policy_version: crate::IMPORT_DISCOVERY_POLICY_VERSION, - }, - None => ImportPlanQueryKey::Ordinary(Box::new(OrdinaryImportPlanKey { - source: ExactSourceInput::new(snapshot), - context: context.clone(), - policy_version: crate::IMPORT_DISCOVERY_POLICY_VERSION, - accepted_reads: accepted_reads.clone(), - carried_ledger: carried_ledger.clone(), - })), - }; - let (plan_dependency, publish_plan) = self.select_import_plan_query(plan_key.clone()); - if let Err(errors) = validate_accepted_read_manifest(snapshot, &accepted_reads) { - let diagnostic_snapshot = self.publish_import_diagnostics( - snapshot, - Some(context.clone()), - None, - carried_ledger.clone(), - accepted_reads.clone(), - &errors, - ); - let attempted_artifact = Arc::new(ImportDiscoveryRevisionArtifact { - status: ImportDiscoveryRevisionStatus::ClosedAttempted, - source_revision: source_revision.clone(), - context: context.clone(), - snapshot: snapshot.clone(), - program: None, - parse_work, - plan: None, - ledger: carried_ledger.clone(), - accepted_reads: accepted_reads.clone(), - graph: None, - diagnostics: errors.clone(), - diagnostic_snapshot: Some(diagnostic_snapshot), - successor_parse: None, - }); - self.publish_import_plan_query( - plan_key, - Err(errors.clone()), - attempted_artifact - .diagnostic_snapshot - .as_ref() - .unwrap() - .clone(), - Some(attempted_artifact), - plan_dependency, - publish_plan, - ); - return Err(errors); - } - // Staging splits into the canonical parse of everything read so far and - // the import-plan construction over the resulting program. Both were - // previously folded into the driver's unattributed region (RUE-786). - let parse_staging_span = tracing::info_span!("import_parse_staging").entered(); - let (parse_result, staged_work, staged_successor_parse) = self.parse_staging_snapshot( - snapshot, - successor - .as_ref() - .map(|(revision, delta)| (*revision, delta)), - ); - drop(parse_staging_span); - parse_work.accumulate(staged_work); - let program = match parse_result { - Ok(program) => program, - Err(errors) => { - let diagnostic_snapshot = self.publish_import_diagnostics( - snapshot, - Some(context.clone()), - None, - carried_ledger.clone(), - accepted_reads.clone(), - &errors, - ); - let attempted_artifact = Arc::new(ImportDiscoveryRevisionArtifact { - status: ImportDiscoveryRevisionStatus::ClosedAttempted, - source_revision: source_revision.clone(), - context: context.clone(), - snapshot: snapshot.clone(), - program: None, - parse_work, - plan: None, - ledger: carried_ledger.clone(), - accepted_reads: accepted_reads.clone(), - graph: None, - diagnostics: errors.clone(), - diagnostic_snapshot: Some(diagnostic_snapshot), - successor_parse: None, - }); - self.publish_import_plan_query( - plan_key, - Err(errors.clone()), - attempted_artifact - .diagnostic_snapshot - .as_ref() - .unwrap() - .clone(), - Some(attempted_artifact), - plan_dependency, - publish_plan, - ); - return Err(errors); - } - }; - // A trusted-toolchain successor stage reuses the committed predecessor - // plan's request groups and constructs groups only for the newly appended - // modules' occurrences; predecessor occurrences are never re-staged. When - // no predecessor plan is retained (an unexpected legacy state) it falls - // back to a full build so the plan is always complete. - let predecessor_plan = new_modules.and_then(|_| { - self.last_good_discovery_artifact() - .and_then(|artifact| artifact.plan.clone()) - }); - let plan_build_span = tracing::info_span!("import_plan_build").entered(); - let plan_build = match (new_modules, predecessor_plan) { - (Some(new_modules), Some(predecessor)) => { - crate::ImportDiscoveryPlan::extend_trusted_successor( - &predecessor, - &program, - context.clone(), - new_modules, - ) - .map(|(plan, constructed)| { - self.import_plan_groups_constructed = self - .import_plan_groups_constructed - .saturating_add(constructed); - plan - }) - } - _ => crate::ImportDiscoveryPlan::new(&program, context.clone()).inspect(|plan| { - self.import_plan_groups_constructed = self - .import_plan_groups_constructed - .saturating_add(plan.groups().len() as u64); - }), - }; - let plan = match plan_build { - Ok(plan) => plan, - Err(error) => { - let errors = CompileErrors::from(error); - let diagnostic_snapshot = self.publish_import_diagnostics( - snapshot, - Some(context.clone()), - None, - carried_ledger.clone(), - accepted_reads.clone(), - &errors, - ); - let attempted_artifact = Arc::new(ImportDiscoveryRevisionArtifact { - status: ImportDiscoveryRevisionStatus::ClosedAttempted, - source_revision: source_revision.clone(), - context: context.clone(), - snapshot: snapshot.clone(), - program: Some(program), - parse_work, - plan: None, - ledger: carried_ledger.clone(), - accepted_reads: accepted_reads.clone(), - graph: None, - diagnostics: errors.clone(), - diagnostic_snapshot: Some(diagnostic_snapshot), - successor_parse: None, - }); - self.publish_import_plan_query( - plan_key, - Err(errors.clone()), - attempted_artifact - .diagnostic_snapshot - .as_ref() - .unwrap() - .clone(), - Some(attempted_artifact), - plan_dependency, - publish_plan, - ); - return Err(errors); - } - }; - drop(plan_build_span); - let _plan_publish_span = tracing::info_span!("import_plan_publish").entered(); - let plan_diagnostics = if publish_plan { - let shape_diagnostics = crate::ImportDiscoveryPlan::shape_diagnostics(&program); - self.publish_import_diagnostics( - snapshot, - Some(context.clone()), - Some(plan.clone()), - carried_ledger.clone(), - accepted_reads.clone(), - &shape_diagnostics, - ) - } else { - let diagnostics = self - .queries - .import_plans - .selected_record(&self.queries.graph) - .expect("selected terminal import plan retains diagnostics") - .diagnostics - .clone(); - self.reuse_diagnostics(diagnostics.clone()); - diagnostics - }; - self.publish_import_plan_query( - plan_key, - Ok(plan.clone()), - plan_diagnostics, - None, - plan_dependency, - publish_plan, - ); - self.open_discovery = Some(Arc::new(ImportDiscoveryRevisionArtifact { - status: ImportDiscoveryRevisionStatus::Open, - source_revision: program.source_revision().clone(), - context, - snapshot: snapshot.clone(), - program: Some(program), - parse_work, - plan: Some(plan.clone()), - ledger: carried_ledger, - accepted_reads, - graph: None, - diagnostics: CompileErrors::new(), - diagnostic_snapshot: None, - successor_parse: staged_successor_parse, - })); - Ok(plan) - } - - /// Close the current staging revision. Missing, ambiguous, and malformed - /// imports retain a closed attempted artifact; only a diagnostic-free graph - /// is atomically adopted as the committed compiler revision. - /// - /// Caller-supplied closure is retained only for the RUE-1033 legacy - /// compatibility boundary. It is not freshness- or speculation-safe; new - /// consumers must publish compiler-ordered canonical frontier batches. - #[cfg(not(test))] - pub fn close_import_discovery( - &mut self, - ledger: crate::ImportObservationLedger, - ) -> Result, CompileErrors> { - self.close_import_discovery_artifact(ledger, None) - .map(|artifact| Arc::new(crate::ImportDiscoveryView::new(artifact))) - } - - #[cfg(test)] - pub(crate) fn close_import_discovery( - &mut self, - ledger: crate::ImportObservationLedger, - ) -> Result, CompileErrors> { - self.close_import_discovery_artifact(ledger, None) - } - - /// Close a strictly-additive trusted-toolchain successor (RUE-1112). The - /// closing ledger is the CURRENT compiler-published view's own carried - /// ledger and the module delta is derived from the opaque capability — the - /// caller supplies nothing but the capability, so no replacement ledger or - /// module set can be substituted. The close projects and reduces only the - /// delta occurrences and merges them into the committed predecessor's closed - /// graph, never re-projecting or re-reducing predecessor occurrences. - pub(crate) fn close_import_discovery_successor( - &mut self, - delta: &TrustedSuccessorDelta, - ) -> Result, CompileErrors> { - let state = self.derive_successor_state(delta)?; - let closed = self - .close_import_discovery_artifact(state.ledger, Some((state.revision, state.delta)))?; - // Consume the single-use delta authority only on a successful close. - self.successor_delta_nonce = None; - Ok(closed) - } - - fn close_import_discovery_artifact( - &mut self, - ledger: crate::ImportObservationLedger, - successor: Option<(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, - ) -> Result, CompileErrors> { - let new_module_ids: Option> = successor.as_ref().map(|(_, delta)| { - delta - .iter() - .map(|revision| revision.module.clone()) - .collect() - }); - let new_modules: Option<&[crate::ModuleId]> = new_module_ids.as_deref(); - let open = self - .open_discovery - .as_deref() - .filter(|artifact| artifact.status == ImportDiscoveryRevisionStatus::Open) - .ok_or_else(|| CompileErrors::from(no_published_program()))? - .clone(); - let plan = open - .plan - .as_ref() - .expect("open discovery attempt retains its plan") - .clone(); - let program = open - .program - .as_ref() - .expect("open discovery attempt retains its program") - .clone(); - // A trusted-toolchain successor close carries the committed predecessor's - // closed graph and projects/reduces only the newly appended modules' - // occurrences. When no predecessor graph is retained it falls back to a - // full close so the committed graph is always complete. - let new_module_set: Option> = - new_modules.map(|modules| modules.iter().cloned().collect()); - let predecessor_graph = new_modules.and_then(|_| { - self.last_good_discovery_artifact() - .and_then(|artifact| artifact.graph.clone()) - }); - let narrow = match (new_module_set, predecessor_graph) { - (Some(set), Some(graph)) => Some((set, graph)), - _ => None, - }; - // A successor projects only the delta occurrences, derived directly from - // the plan's delta segment — never by filtering the merged plan. - let roots = match &narrow { - Some(_) => plan.delta_roots(), - None => crate::ImportDemandRoots::whole_plan(&plan), - }; - let exact_groups = match self.queries.revisioned.current_import_revision() { - Some(revision) => match self - .queries - .revisioned - .exact_import_groups(revision, &roots) - { - Ok(groups) => groups, - Err(error) => { - let errors = CompileErrors::from(error); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - }, - // RUE-1033 LEGACY EMBEDDER GATE: callers that bypass the canonical - // begin/frontier/publish protocol retain their historical plan - // groups until that entire public boundary is removed together. - None => plan.groups().to_vec(), - }; - // The predecessor ledger portion was validated at the predecessor close; - // a successor close validates and reduces only the newly appended - // occurrences' observations. Those observations are gathered directly from - // the plan's delta groups (O(delta)), never by scanning the full carried - // ledger. The full `ledger` is still what the committed artifact carries. - let narrow_ledger = match &narrow { - Some(_) => { - let new_observations = plan - .delta_groups() - .iter() - .flat_map(|group| group.iter()) - .filter_map(|request| ledger.get(request).cloned()) - .collect::>(); - let mut filtered = crate::ImportObservationLedger::default(); - let mut record_error = None; - for observation in new_observations { - if let Err(error) = filtered.record(observation) { - record_error = Some(error); - break; - } - } - if let Some(error) = record_error { - let errors = CompileErrors::from(error); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - Some(filtered) - } - None => None, - }; - let check_ledger = narrow_ledger.as_ref().unwrap_or(&ledger); - if let Err(error) = - crate::import_discovery::validate_exact_import_ledger(&exact_groups, check_ledger) - { - let errors = CompileErrors::from(error); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - if !crate::import_discovery::exact_import_pending_requests(&exact_groups, check_ledger) - .is_empty() - { - let errors = - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - "import discovery ledger is incomplete; the attempted revision cannot close" - .into(), - ))); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - let diagnostics = crate::import_discovery::exact_import_diagnostics( - &program, - &exact_groups, - check_ledger, - ); - if crate::import_discovery::exact_import_has_failures(&exact_groups, check_ledger) { - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &diagnostics, - ); - return Err(diagnostics); - } - - // A successor shares the committed predecessor's module-resolution table - // by reference and appends only the delta modules (looked up by identity), - // so the complete table is never reconstructed or re-sorted. A full close - // builds the whole table. - let resolution_build = match &narrow { - Some((set, predecessor)) => { - let delta: Vec = set - .iter() - .filter_map(|module_id| program.module(module_id)) - .map(|module| crate::ModuleResolutionInput { - module: module.module_id().clone(), - physical_path: Arc::from(module.physical_path()), - }) - .collect(); - crate::ModuleResolutionInputs::extend_successor( - &predecessor.input().resolution, - delta, - ) - } - None => crate::ModuleResolutionInputs::new( - program.root().clone(), - program - .modules() - .iter() - .map(|module| crate::ModuleResolutionInput { - module: module.module_id().clone(), - physical_path: Arc::from(module.physical_path()), - }) - .collect(), - ), - }; - let resolution = match resolution_build { - Ok(resolution) => resolution, - Err(error) => { - let errors = CompileErrors::from(error); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - }; - let input = ImportGraphInputDescriptor { - sources: program.source_revision().clone(), - resolution, - std_dir: open.context.std_root().map(Arc::from), - }; - // Reduce only the projected occurrences: the whole plan for a full close, - // or exactly the newly appended modules' occurrences for a trusted-toolchain - // successor. `reduced` therefore holds the new records in successor mode. - let reduced = match crate::import_discovery::reduce_exact_import_graph( - program.root().clone(), - &exact_groups, - check_ledger, - &open.accepted_reads, - ) { - Ok(graph) => graph, - Err(error) => { - let errors = CompileErrors::from(error); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - }; - self.import_close_records_reduced = self - .import_close_records_reduced - .saturating_add(reduced.records().len() as u64); - // In successor mode, merge the new records into the committed predecessor's - // closed graph and validate incrementally (the predecessor topology is - // carried, never re-walked). A full close reduces and validates the whole - // graph directly. - let (reduced, validation) = match &narrow { - Some((set, predecessor)) => { - // The reduction produced only the delta records; build the - // successor graph by structurally sharing the predecessor's record - // segment (no predecessor record is copied or re-sorted) and - // validate only the delta against the carried predecessor result. - let new_records = reduced.records().to_vec(); - let merged = crate::CanonicalImportGraph::from_additive_successor( - program.root().clone(), - predecessor.graph(), - new_records.clone(), - ); - let validation = crate::validate_additive_successor( - predecessor.validation(), - &new_records, - &input.resolution, - set, - ); - (merged, validation) - } - None => { - let validation = validate_canonical_import_graph(&reduced, &input.resolution); - (reduced, validation) - } - }; - let graph = Arc::new(CanonicalImportGraphOutput { - input: input.clone(), - graph: reduced, - validation, - }); - let resolution_only = graph.validation().problems().iter().all(|problem| { - matches!( - problem, - crate::CanonicalImportGraphProblem::MissingResolution { .. } - | crate::CanonicalImportGraphProblem::AmbiguousResolution { .. } - ) - }); - if !resolution_only { - let mut errors = diagnostics; - errors.push(CompileError::without_span(ErrorKind::InvalidCompilerInput( - "import discovery produced a structurally invalid canonical graph".into(), - ))); - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - if !graph.validation().is_valid() || !diagnostics.is_empty() { - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - Some(graph), - &diagnostics, - ); - return Err(diagnostics); - } - - let adoption = self.adopt_discovery_program_for_presentation( - &open.snapshot, - program.clone(), - open.parse_work, - successor.is_some(), - open.successor_parse.clone(), - ); - if let Err(errors) = adoption.into_result() { - self.publish_failed_import_attempt( - open, - plan, - ledger, - successor.as_ref(), - ImportDiscoveryRevisionStatus::ClosedAttempted, - None, - &errors, - ); - return Err(errors); - } - let (closure_key, closure_dependencies, publish_closure) = - self.select_import_closure_query(&open, &plan, &ledger, successor.as_ref()); - let diagnostic_snapshot = self.publish_import_diagnostics( - &open.snapshot, - Some(open.context.clone()), - Some(plan), - ledger.clone(), - open.accepted_reads.clone(), - &diagnostics, - ); - let artifact = Arc::new(ImportDiscoveryRevisionArtifact { - status: ImportDiscoveryRevisionStatus::ClosedValid, - ledger, - graph: Some(graph.clone()), - diagnostics, - diagnostic_snapshot: Some(diagnostic_snapshot), - ..open - }); - self.publish_import_closure_query( - closure_key, - Ok(graph), - artifact.clone(), - closure_dependencies, - publish_closure, - ); - self.open_discovery = None; - // A committed close is a lineage boundary: additions recorded before it - // belong to the closed graph, so the recorded-additions lineage resets. - self.queries.revisioned.clear_lineage_additions(); - // Record the closed state for a possible trusted-toolchain continuation - // (RUE-1112), but only when the canonical begin/frontier/publish - // protocol produced a current import-input revision — legacy embedders - // that bypass it get no continuation. The state retains everything the - // successor verification needs (predecessor snapshot, context, accepted - // reads, carried ledger) so the check is record-only, never filesystem. - // - // The closed state is deliberately NON-AUTHORIZING here (`attached_demands` - // is `None`): a close by itself mints no token and authorizes no successor. - // Only a subsequent rooted semantic park attaches its exact missing-demand - // set to this same state, so a later close whose attempt never parks can - // never inherit an earlier park's authority. - self.continuation = self - .queries - .revisioned - .current_import_revision() - .map(|revision| { - self.next_continuation_nonce += 1; - ContinuationState { - nonce: self.next_continuation_nonce, - revision, - snapshot: artifact.snapshot().clone(), - accepted_reads: artifact.accepted_read_manifest().clone(), - ledger: artifact.ledger().clone(), - attached_demands: None, - } - }); - Ok(artifact) - } - - /// Mint the trusted-toolchain continuation token for the current successful - /// import-discovery close, if one is outstanding AND authorizing (RUE-1112). - /// A closed state becomes authorizing only once a rooted semantic park - /// has attached its exact missing-demand set; a close whose attempt is ready - /// (or never parked) mints no token. The token is opaque and single-use; the - /// host hands it back to [`Self::publish_trusted_toolchain_successor`]. - pub(crate) fn closed_discovery_continuation(&self) -> Option { - self.continuation - .as_ref() - .filter(|state| state.attached_demands.is_some()) - .map(|state| ClosedDiscoveryContinuation { - session: self.identity.clone(), - nonce: state.nonce, - revision: state.revision, - }) - } - - /// Publish exactly one strictly-additive trusted-toolchain successor on the - /// continuation's closed revision (RUE-1112). - /// - /// The host has already done all filesystem work through the B4-hardened - /// path — read each demanded module, checked containment/manifest/stable-read - /// provenance, assembled the successor snapshot and accepted-read records. - /// This verifies that work purely from records (no filesystem access) by - /// diffing the successor against the continuation's predecessor, then - /// publishes in the SAME request generation carrying the predecessor ledger - /// unchanged. The added leaves carry no observation in the predecessor - /// ledger yet; discovery of the `@import` edges they introduce (a trusted - /// leaf such as `strbuf.rue` imports `option.rue`/`arraybuf.rue`/`rawbuf.rue`) - /// is the driver's subsequent re-close, which roots its frontier only in - /// these new leaves. - /// - /// Returns the successor revision together with the exact set of module IDs - /// it appended (the verified `added == demanded` set). The re-close uses that - /// set as the sole discovery frontier roots, so the predecessor import - /// topology is never re-rooted or re-resolved. - pub(crate) fn publish_trusted_toolchain_successor( - &mut self, - token: ClosedDiscoveryContinuation, - issued_frontier: &crate::ImportDemandFrontier, - successor: &SourceSnapshot, - accepted_reads: crate::AcceptedReadManifest, - ) -> Result { - let reject = |message: &str| { - CompileErrors::from(crate::CompileError::without_span( - rue_error::ErrorKind::InvalidCompilerInput(format!( - "trusted-toolchain successor rejected: {message}" - )), - )) - }; - - // Same session. - if !Arc::ptr_eq(&token.session, &self.identity) { - return Err(reject("continuation token belongs to a different session")); - } - // Token current + unused. Peek without consuming so a rejected batch - // leaves the token valid for a corrected retry; only a successful publish - // consumes it (a reused token then finds no outstanding state). - let state = match self.continuation.as_ref() { - Some(state) if token.nonce == state.nonce && token.revision == state.revision => { - state.clone() - } - Some(_) => { - return Err(reject( - "continuation token is stale (superseded by a newer close or request)", - )); - } - None => { - return Err(reject( - "no outstanding closed-discovery continuation; the token was already used or invalidated", - )); - } - }; - - // The closure witness: the empty rooted frontier of the token's closed - // revision. Only a genuinely-closed predecessor may continue. - if issued_frontier.mode() != crate::ImportDemandMode::Rooted { - return Err(reject("the closure witness frontier must be rooted")); - } - if issued_frontier.revision() != state.revision { - return Err(reject( - "the closure witness frontier does not belong to the continuation's revision", - )); - } - if !issued_frontier.requests().is_empty() { - return Err(reject( - "the closure witness frontier is not empty; the predecessor did not close", - )); - } - - // Same compilation root (the context/read policy is carried unchanged - // into the successor below). - if successor.source_revision().root() != state.snapshot.source_revision().root() { - return Err(reject("the successor changed the compilation root")); - } - - // Strict additive source evolution: every predecessor module revision - // must appear byte-identical in the successor; the additions are exactly - // the new leaves. - let old_modules: std::collections::BTreeSet<&crate::ModuleRevision> = - state.snapshot.source_revision().modules().iter().collect(); - let new_modules: std::collections::BTreeSet<&crate::ModuleRevision> = - successor.source_revision().modules().iter().collect(); - if !old_modules.is_subset(&new_modules) { - return Err(reject( - "a predecessor module revision was mutated or removed (source evolution must be strictly additive)", - )); - } - let additions: Vec<&crate::ModuleRevision> = - new_modules.difference(&old_modules).copied().collect(); - if additions.is_empty() { - return Err(reject( - "a trusted-toolchain successor must add at least one leaf", - )); - } - - // Every predecessor accepted-read entry must appear byte-identical in the - // successor manifest (altered old provenance rejected). - let new_reads: std::collections::HashSet<&crate::AcceptedReadManifestEntry> = - accepted_reads.iter().collect(); - for old in state.accepted_reads.iter() { - if !new_reads.contains(old) { - return Err(reject( - "a predecessor accepted-read provenance entry was altered or removed", - )); - } - } - - // Demand authority lives only in the attached park set. A close whose - // rooted attempt never parked is non-authorizing: it may not consume the - // token or admit any leaf, so a later ready close can never reuse an - // earlier park's demands. - let Some(attached_demands) = state.attached_demands.as_ref() else { - return Err(reject( - "the closed continuation is not authorizing; no rooted semantic park has attached a demanded-module set", - )); - }; - - // Every addition is a trusted standard-library leaf with well-formed - // accepted-read provenance in the successor manifest. - for addition in &additions { - if !addition.module.is_trusted_standard_library() { - return Err(reject( - "an added leaf is not a trusted standard-library module", - )); - } - if !accepted_reads - .iter() - .any(|entry| entry.module() == &addition.module) - { - return Err(reject( - "an added trusted leaf has no accepted-read provenance", - )); - } - } - - // The successor's added module-ID set must EQUAL the park's demanded - // missing set — set equality, not per-member membership. This enforces - // the one-park/one-batched-successor contract in both directions: an - // arbitrary or uninvited module (added ⊄ demanded) is rejected, and a - // partial batch that omits a demanded member (demanded ⊄ added) is - // rejected WITHOUT consuming the single-use token (the peek above only - // consumes on the successful publish below). - let demanded: std::collections::BTreeSet = attached_demands - .iter() - .map(|demand| demand.trusted_module_id()) - .collect::>() - .map_err(CompileErrors::from)?; - let added: std::collections::BTreeSet = additions - .iter() - .map(|addition| addition.module.clone()) - .collect(); - if added != demanded { - return Err(reject( - "the successor's added trusted modules must equal the rooted park's demanded missing set exactly (one park, one batched successor)", - )); - } - - // Publish the strictly-additive successor in the SAME request generation - // as a sparse overlay over the predecessor view: the carried ledger and - // topology are inherited unchanged, only the verified added leaves' - // source/provenance leaves are published, and the overlay re-derives the - // additions from the published parent view (they must equal `added`). - let published = self - .queries - .revisioned - .publish_trusted_successor_view( - state.revision, - successor, - accepted_reads, - state.ledger.clone(), - &added, - state.revision.frontier_round + 1, - ) - .map_err(CompileErrors::from)?; - // Consume the single-use continuation only on success. - self.continuation = None; - // Mint the opaque successor-delta authority from the VERIFIED `added` - // set (equal to the park's demanded missing set). `BTreeSet` iteration is - // sorted, so the appended roots are deterministic. The host receives only - // this opaque value; it cannot inspect or edit the module identities. - let appended: Arc<[crate::ModuleId]> = added.into_iter().collect::>().into(); - self.next_continuation_nonce += 1; - let nonce = self.next_continuation_nonce; - self.successor_delta_nonce = Some(nonce); - Ok(TrustedSuccessorDelta { - session: self.identity.clone(), - nonce, - revision: published, - appended, - }) - } - - fn publish_failed_import_attempt( - &mut self, - open: ImportDiscoveryRevisionArtifact, - plan: crate::ImportDiscoveryPlan, - ledger: crate::ImportObservationLedger, - successor: Option<&(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, - status: ImportDiscoveryRevisionStatus, - graph: Option>, - errors: &CompileErrors, - ) -> Arc { - debug_assert_ne!(status, ImportDiscoveryRevisionStatus::ClosedValid); - let (closure_key, closure_dependencies, publish_closure) = - self.select_import_closure_query(&open, &plan, &ledger, successor); - let diagnostic_snapshot = self.publish_import_diagnostics( - &open.snapshot, - Some(open.context.clone()), - Some(plan), - ledger.clone(), - open.accepted_reads.clone(), - errors, - ); - let artifact = Arc::new(ImportDiscoveryRevisionArtifact { - status, - ledger, - graph, - diagnostics: errors.clone(), - diagnostic_snapshot: Some(diagnostic_snapshot), - ..open - }); - self.publish_import_closure_query( - closure_key, - Err(errors.clone()), - artifact.clone(), - closure_dependencies, - publish_closure, - ); - self.open_discovery = None; - artifact - } - - fn require_closed_discovery(&self) -> Result<(), CompileErrors> { - if self - .discovery_attempt_artifact() - .is_some_and(|attempt| attempt.status != ImportDiscoveryRevisionStatus::ClosedValid) - { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "semantic and dependency queries require a closed valid discovery revision" - .into(), - ), - ))); - } - Ok(()) - } - pub(crate) fn work(&self) -> &CompilerSessionWork { - self.metrics.work() - } - /// Return an owned snapshot of explicitly unstable compiler metrics. - /// - /// The snapshot cannot be installed back into this or another session and - /// therefore grants no access to query ownership or invalidation state. - pub fn unstable_metrics(&self) -> crate::unstable::MetricsSnapshot { - crate::unstable::MetricsSnapshot::new(self.metrics.work().clone()) - } - /// Diagnostic snapshot from the most recently attempted query, whether it - /// succeeded or failed. - pub fn latest_diagnostics(&self) -> Option<&Arc> { - self.diagnostics.latest() - } - /// Most recently queried diagnostic snapshot with no errors. - pub fn latest_successful_diagnostics(&self) -> Option<&Arc> { - self.diagnostics.latest_successful() - } - /// Most recent successful semantic diagnostic snapshot. - /// - /// Syntax or semantic failures never replace this last-good semantic - /// baseline. A caller may clone the returned `Arc` to pin it independently - /// of later session eviction. - pub fn last_good_semantic_diagnostics(&self) -> Option<&Arc> { - self.diagnostics.last_good_semantic() - } - - /// Durable declaration cache retained by the last successful semantic - /// record. - #[cfg(test)] - fn last_good_durable_declaration_cache(&self) -> Option<&DurableDeclarationCache> { - self.queries - .semantic - .last_good_record() - .and_then(|entry| entry.durable_declaration_cache.as_ref()) - } - - /// Supply an explicit durable baseline for the next semantic attempt, - /// replacing the last-good record as the reuse source. - /// - /// No production path calls this. It is the injection seam that lets a test - /// drive a chosen — including deliberately stale or corrupt — durable cache - /// through the production reuse path, instead of a `cfg(test)` branch that - /// production never compiles (RUE-1143). - #[cfg(test)] - fn set_durable_baseline_override(&mut self, baseline: Option) { - self.durable_baseline_override = baseline; - } - /// Look up the currently selected, or otherwise most recently indexed, - /// diagnostic batch matching a source-attempt and public query stage. - /// - /// Canonical and presentation-ordered producer attempts can share the same - /// public stage. When the current selection matches, this returns that - /// exact batch; otherwise it returns the most recently indexed match. - /// Clone the `Arc` when the artifact must outlive index eviction. - #[cfg(test)] - pub(crate) fn most_recent_diagnostics_for( - &self, - source: &SourceSnapshot, - stage: &FrontendDiagnosticIdentity, - ) -> Option<&Arc> { - self.diagnostics.find(source, stage) - } - - /// Compatibility name for [`Self::most_recent_diagnostics_for`]. - /// - /// This is not an exact lookup when canonical and presentation provenance - /// share a public stage; it follows the selection contract documented by - /// `most_recent_diagnostics_for`. - #[cfg(test)] - pub(crate) fn diagnostics_for( - &self, - source: &SourceSnapshot, - stage: &FrontendDiagnosticIdentity, - ) -> Option<&Arc> { - self.most_recent_diagnostics_for(source, stage) - } - - fn publish_diagnostics( - &mut self, - source: &SourceSnapshot, - stage: FrontendDiagnosticIdentity, - errors: Option<&CompileErrors>, - warnings: &[CompileWarning], - ) -> Arc { - let provenance = match &stage { - FrontendDiagnosticIdentity::Syntax => self - .batch_diagnostic_order - .as_ref() - .map_or(DiagnosticAttemptProvenance::Canonical, |order| { - DiagnosticAttemptProvenance::Presentation(order.clone()) - }), - FrontendDiagnosticIdentity::Merge if errors.is_some() => self - .batch_diagnostic_order - .as_ref() - .map_or(DiagnosticAttemptProvenance::Canonical, |order| { - DiagnosticAttemptProvenance::Presentation(order.clone()) - }), - FrontendDiagnosticIdentity::Merge => DiagnosticAttemptProvenance::Canonical, - FrontendDiagnosticIdentity::Import(_) - | FrontendDiagnosticIdentity::Rir(_) - | FrontendDiagnosticIdentity::Semantic(_) => DiagnosticAttemptProvenance::Canonical, - }; - if let Some(existing) = self - .diagnostics - .find_exact(source, &stage, &provenance) - .cloned() - { - self.metrics.diagnostic_reuse(); - self.diagnostics.select_snapshot(&existing); - self.refresh_retention_metrics(); - return existing; - } - let invalidated_previous = self.diagnostics.latest().is_some(); - let snapshot = Arc::new(FrontendDiagnosticSnapshot { - source: source.clone(), - stage, - provenance, - errors: errors - .map(|errors| errors.iter().cloned().collect::>()) - .unwrap_or_default() - .into(), - warnings: warnings.to_vec().into(), - }); - self.metrics.diagnostic_publication(invalidated_previous); - self.refresh_retention_metrics(); - snapshot - } - - fn reuse_diagnostics(&mut self, snapshot: Arc) { - self.metrics.diagnostic_reuse(); - self.diagnostics.select_snapshot(&snapshot); - self.refresh_retention_metrics(); - } - - fn publish_import_diagnostics( - &mut self, - source: &SourceSnapshot, - context: Option, - plan: Option, - ledger: crate::ImportObservationLedger, - accepted_reads: crate::AcceptedReadManifest, - errors: &CompileErrors, - ) -> Arc { - let input = ImportDiagnosticInputDescriptor { - source: source.source_revision().clone(), - context, - plan, - ledger, - accepted_reads, - }; - self.publish_diagnostics( - source, - FrontendDiagnosticIdentity::Import(input), - Some(errors), - &[], - ) - } - - fn refresh_retention_metrics(&mut self) { - let diagnostics = self.diagnostics.retention_metrics(); - - let stores = [ - self.queries.revisioned.parse_retention(), - self.queries.import_plans.retention(&self.queries.graph), - self.queries.import_closures.retention(&self.queries.graph), - self.queries - .import_diagnostics - .retention(&self.queries.graph), - self.queries.merge.retention(&self.queries.graph), - self.queries.rir.retention(&self.queries.graph), - self.queries.semantic.retention(&self.queries.graph), - self.queries.definitions.retention(&self.queries.graph), - self.queries.manifests.retention(&self.queries.graph), - self.queries - .invalidation_plans - .retention(&self.queries.graph), - ]; - - let mut pinned_attempts = BTreeSet::new(); - pinned_attempts.extend(self.queries.revisioned.parse.origin_attempt_ids()); - pinned_attempts.extend(self.queries.import_plans.origin_attempt_ids()); - pinned_attempts.extend(self.queries.import_closures.origin_attempt_ids()); - pinned_attempts.extend(self.queries.import_diagnostics.origin_attempt_ids()); - pinned_attempts.extend(self.queries.merge.origin_attempt_ids()); - pinned_attempts.extend(self.queries.rir.origin_attempt_ids()); - pinned_attempts.extend(self.queries.semantic.origin_attempt_ids()); - pinned_attempts.extend(self.queries.definitions.origin_attempt_ids()); - pinned_attempts.extend(self.queries.manifests.origin_attempt_ids()); - pinned_attempts.extend(self.queries.invalidation_plans.origin_attempt_ids()); - self.metrics.set_pinned_origins(pinned_attempts); - - let mut manifests = BTreeSet::new(); - for entry in self.queries.manifests.records() { - if let Ok(manifest) = &entry.result { - manifests.insert(Arc::as_ptr(manifest) as usize); - } - } - for entry in self.queries.invalidation_plans.records() { - manifests.insert(Arc::as_ptr(&entry.key.previous) as usize); - manifests.insert(Arc::as_ptr(&entry.key.current) as usize); - } - self.metrics.set_retention(FrontendRetentionMetrics { - retained_query_records: stores.iter().map(|store| store.retained).sum(), - protected_query_records: stores.iter().map(|store| store.protected).sum(), - dependency_pins: stores.iter().map(|store| store.pinned).sum(), - validation_tombstones: stores.iter().map(|store| store.tombstones).sum(), - graph_retained_disappeared_nodes: self.queries.graph.retained_disappeared_count(), - query_evictions: stores.iter().map(|store| store.evictions).sum(), - aborted_query_attempts: self.queries.revisioned.parse.retained_aborted_len() - + self.queries.import_plans.aborted_len() - + self.queries.import_closures.aborted_len() - + self.queries.import_diagnostics.aborted_len() - + self.queries.merge.aborted_len() - + self.queries.rir.aborted_len() - + self.queries.semantic.aborted_len() - + self.queries.definitions.aborted_len() - + self.queries.manifests.aborted_len() - + self.queries.invalidation_plans.aborted_len(), - import_query_entries: self.queries.import_diagnostics.len(), - import_query_evictions: self.queries.import_diagnostics.evictions(), - semantic_query_entries: self.queries.semantic.len(), - semantic_query_evictions: self.queries.semantic.evictions(), - definition_query_entries: self.queries.definitions.len(), - definition_query_evictions: self.queries.definitions.evictions(), - diagnostic_entries: diagnostics.entries, - diagnostic_source_attempts: diagnostics.source_attempts, - diagnostic_source_bytes: diagnostics.source_bytes, - dependency_manifests: manifests.len(), - invalidation_plans: self.queries.invalidation_plans.len(), - }); - } - - pub fn update(&mut self, snapshot: &SourceSnapshot) -> CompilerSessionUpdate { - // A source update supersedes the predecessor any outstanding - // trusted-toolchain continuation or successor-delta authority was - // issued against (RUE-1112): a stale capability can neither stage nor - // close over an artifact the update replaced. - self.continuation = None; - self.successor_delta_nonce = None; - self.select_diagnostic_presentation(None); - let provenance = self.syntax_diagnostic_provenance(); - self.run_parse_update(snapshot, provenance) - } - - /// Publish a snapshot while retaining its caller-selected presentation order. - /// - /// Query artifacts still use stable module identity. Only syntax and merge - /// diagnostic ordering follows [`SourceSnapshot::files`], which is useful - /// for command-line and other presentation-oriented consumers. - pub(crate) fn update_for_presentation( - &mut self, - snapshot: &SourceSnapshot, - ) -> CompilerSessionUpdate { - // A presentation update replaces the retained parse artifact exactly - // like a source update, so it likewise supersedes any outstanding - // trusted-toolchain continuation or successor-delta authority - // (RUE-1112). - self.continuation = None; - self.successor_delta_nonce = None; - self.select_diagnostic_presentation(Some(crate::shared_segments::SharedList::flat( - snapshot - .files() - .map(|source| snapshot.module_id(source.file_id).unwrap().clone()) - .collect(), - ))); - let provenance = self.syntax_diagnostic_provenance(); - self.run_parse_update(snapshot, provenance) - } - - fn adopt_discovery_program_for_presentation( - &mut self, - snapshot: &SourceSnapshot, - _program: Arc, - _work: ParsedModulesWork, - successor: bool, - retained_successor_parse: Option, - ) -> CompilerSessionUpdate { - // A trusted-successor close adopts by RE-SELECTING the exact successor - // parse terminal its stage computed and retained on the open artifact - // — same key, same revision — never by re-deriving an extension - // against the now-selected successor state (which would mint a second - // empty-extension terminal). A missing retained terminal rejects the - // close. - if successor { - return match retained_successor_parse { - Some(record) => self.run_parse_update_successor(snapshot, record), - None => { - let errors = CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "trusted-toolchain successor close rejected: the staged successor parse terminal is not retained".into(), - ), - )); - let diagnostics = Arc::new(FrontendDiagnosticSnapshot { - source: snapshot.clone(), - stage: FrontendDiagnosticIdentity::Syntax, - provenance: DiagnosticAttemptProvenance::Canonical, - errors: errors.as_slice().to_vec().into(), - warnings: Arc::from([]), - }); - CompilerSessionUpdate { - result: Err(errors), - work: ParsedModulesWork::default(), - #[cfg(test)] - invalidation: ParseInvalidationSummary::default(), - downstream_invalidated: false, - diagnostics, - } - } - }; - } - self.select_diagnostic_presentation(Some(crate::shared_segments::SharedList::flat( - snapshot - .files() - .map(|source| snapshot.module_id(source.file_id).unwrap().clone()) - .collect(), - ))); - let provenance = self.syntax_diagnostic_provenance(); - self.run_parse_update(snapshot, provenance) - } - - fn parse_baseline(&self) -> Option> { - self.queries - .revisioned - .parse - .last_good_record() - .and_then(|record| record.result.as_ref().ok()) - .cloned() - } - - fn parse_invalidation(&self, snapshot: &SourceSnapshot) -> ParseInvalidationSummary { - let baseline = self.parse_baseline(); - classify_invalidation(snapshot, baseline.as_deref()) - } - - fn syntax_diagnostic_provenance(&self) -> DiagnosticAttemptProvenance { - self.batch_diagnostic_order - .as_ref() - .map_or(DiagnosticAttemptProvenance::Canonical, |order| { - DiagnosticAttemptProvenance::Presentation(order.clone()) - }) - } - - fn select_diagnostic_presentation( - &mut self, - order: Option>, - ) { - self.batch_diagnostic_order = order; - } - - fn execute_parse_query( - &mut self, - snapshot: &SourceSnapshot, - presentation: DiagnosticAttemptProvenance, - attempt_id: AttemptId, - ) -> ( - ParseQueryRecord, - Arc, - QueryAttemptExecution, - ParsedModulesWork, - ParseInvalidationSummary, - ) { - // Keying, module parsing, and terminal publication are separate costs - // inside the parse query. Timing them apart keeps the staging residual - // from hiding whole-snapshot content hashing behind `parse_file` - // (RUE-786). - let key_span = tracing::info_span!("parse_query_key").entered(); - let source = ExactSourceInput::new(snapshot); - // An ordinary key carries every file's exact content identity, so the - // typed store hashes and compares each of them. - self.parse_key_entries_compared = self - .parse_key_entries_compared - .saturating_add(snapshot.len() as u64); - let key = ParseQueryKey::Ordinary(Box::new(OrdinaryParseKey { - source: source.clone(), - file_order: snapshot - .files() - .map(|source| source.file_id) - .collect::>() - .into(), - presentation: presentation.clone(), - })); - let revision = self.queries.revisioned.source_revision(&source, snapshot); - let demanded_modules = match &presentation { - DiagnosticAttemptProvenance::Canonical => snapshot - .source_revision() - .modules() - .iter() - .map(|source| source.module.clone()) - .collect::>(), - DiagnosticAttemptProvenance::Presentation(order) => order.iter().cloned().collect(), - }; - self.parse_sources_materialized = self - .parse_sources_materialized - .saturating_add(demanded_modules.len() as u64); - self.parse_modules_dispatched = self - .parse_modules_dispatched - .saturating_add(demanded_modules.len() as u64); - drop(key_span); - let (modular_result, modular_work) = { - let _span = tracing::info_span!("parse_program").entered(); - self.queries.revisioned.parse_program( - revision, - snapshot.source_revision().root(), - demanded_modules, - ) - }; - let _commit_span = tracing::info_span!("parse_query_commit").entered(); - self.parse_invalidation_entries_compared = self - .parse_invalidation_entries_compared - .saturating_add(snapshot.len() as u64); - let prepared = self.queries.revisioned.parse.prepare(key.clone()); - let baseline = self.parse_baseline(); - let attempt = prepared.execute(revision, attempt_id, |context| { - context.input(rue_query::InputIdentity::new( - crate::revisioned_query_database::RevisionedQueryDatabase::SOURCE_INPUT, - "current", - ))?; - let work = modular_work; - let invalidation = classify_invalidation(snapshot, baseline.as_deref()); - let result = modular_result; - // Freeze diagnostics privately with the query output. Session - // selection and metrics happen only after atomic publication. - let diagnostics = Arc::new(FrontendDiagnosticSnapshot { - source: snapshot.clone(), - stage: FrontendDiagnosticIdentity::Syntax, - provenance: presentation.clone(), - errors: result - .as_ref() - .err() - .map_or_else(|| Arc::from([]), |errors| errors.as_slice().to_vec().into()), - warnings: Arc::from([]), - }); - Ok(ParseQueryRecord { - key, - runtime_revision: revision, - snapshot: snapshot.clone(), - result, - diagnostics, - work, - invalidation, - }) - }); - self.queries.revisioned.select_parse(&attempt); - let terminal = attempt - .terminal() - .unwrap_or_else(|| panic!("parse query aborted: {:?}", attempt.abort())); - let record = match terminal.outcome() { - rue_query::QueryOutcome::Success(record) => record.clone(), - rue_query::QueryOutcome::Failure(_) => unreachable!("parse retains typed records"), - }; - let execution = match attempt.execution() { - rue_query::RequestExecution::Computed => { - self.metrics - .diagnostic_publication(self.diagnostics.latest().is_some()); - QueryAttemptExecution::Computed - } - rue_query::RequestExecution::Reused | rue_query::RequestExecution::Joined => { - self.reuse_diagnostics(record.diagnostics.clone()); - QueryAttemptExecution::Reused - } - rue_query::RequestExecution::Aborted => unreachable!(), - }; - let work = if execution == QueryAttemptExecution::Computed { - record.work - } else { - ParsedModulesWork::default() - }; - let invalidation = if execution == QueryAttemptExecution::Computed { - record.invalidation.clone() - } else { - self.parse_invalidation(snapshot) - }; - let view = self.queries.revisioned.parse.attempt_view( - attempt_id, - attempt, - QueryStructuralWork::Parse(work), - ); - self.diagnostics.select(view.clone()); - (record, view, execution, work, invalidation) - } - - /// Reconcile one successor parse extension without side effects: the - /// retained parse artifact this stage extends (within one trusted re-close, - /// the committed predecessor for the first stage and the prior successor - /// stage after a frontier batch), its presentation order, and the appended - /// (module, file) pairs. The retained artifact must PROVE it is the - /// successor snapshot's structural ancestor — every one of its source - /// segments carried by `Arc` identity, same root, and its exact - /// presentation order — so a parse record from any other snapshot (an - /// intervening source or presentation update) can never be extended; the - /// capability is rejected instead. Everything here is O(appended); content - /// identity is pinned by the published revision, never re-hashed or - /// re-compared. - fn prepare_successor_parse( - &self, - snapshot: &SourceSnapshot, - delta: &Arc<[crate::ModuleRevision]>, - ) -> Result { - let reject = |message: &str| { - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - format!("trusted-toolchain successor parse rejected: {message}"), - ))) - }; - let Some(terminal) = self.queries.revisioned.parse.last_good_terminal() else { - return Err(reject("no predecessor parse artifact is retained")); - }; - let Ok(predecessor_terminal) = self - .queries - .revisioned - .parse - .family_handle() - .adoptable_terminal(terminal) - else { - return Err(reject( - "the retained predecessor parse terminal is not adoptable", - )); - }; - let rue_query::QueryOutcome::Success(record) = predecessor_terminal.terminal().outcome() - else { - return Err(reject("the retained predecessor parse artifact failed")); - }; - let Ok(predecessor_program) = record.result.as_ref().cloned() else { - return Err(reject("the retained predecessor parse artifact failed")); - }; - let DiagnosticAttemptProvenance::Presentation(predecessor_order) = - &record.diagnostics.provenance - else { - return Err(reject( - "the retained parse artifact carries no staging presentation order", - )); - }; - let predecessor_order = predecessor_order.clone(); - let predecessor_revision = record.runtime_revision; - // STRUCTURAL ANCESTRY: the successor snapshot must carry every source - // segment of the retained artifact's snapshot by `Arc` identity, with - // the same root. An artifact retained by an intervening update over a - // different or reordered snapshot cannot share this lineage and is - // rejected here rather than silently extended. - let predecessor_snapshot = record.snapshot.clone(); - { - let successor_segments = snapshot.source_revision().module_segments().segments(); - let predecessor_segments = predecessor_snapshot - .source_revision() - .module_segments() - .segments(); - let shared_prefix = successor_segments.len() >= predecessor_segments.len() - && successor_segments - .iter() - .zip(predecessor_segments.iter()) - .all(|(a, b)| Arc::ptr_eq(a, b)); - if !shared_prefix - || snapshot.source_revision().root() - != predecessor_snapshot.source_revision().root() - { - return Err(reject( - "the retained parse artifact is not the successor snapshot's structural ancestor", - )); - } - } - let predecessor_len = predecessor_program.modules_len(); - if predecessor_len != predecessor_snapshot.len() - || predecessor_order.len() != predecessor_len - { - return Err(reject( - "the retained parse artifact does not cover its own snapshot", - )); - } - // A re-stage whose snapshot appended nothing since the retained parse - // (a frontier round that only grew observations) extends with an empty - // delta and reuses every retained module. - if predecessor_len > snapshot.len() || snapshot.len() - predecessor_len > delta.len() { - return Err(reject( - "the successor snapshot does not extend the retained parse artifact by the authorized delta", - )); - } - // The appended sources extend the predecessor's dense file table, so - // the appended (module, file) pairs are exactly the tail file IDs. - let mut appended = Vec::with_capacity(snapshot.len() - predecessor_len); - for index in predecessor_len as u32 + 1..=snapshot.len() as u32 { - let file_id = crate::FileId::new(index); - let Some(module) = snapshot.module_id(file_id) else { - return Err(reject("an appended source has no logical module")); - }; - appended.push((module.clone(), file_id)); - } - // Every appended module revision must be one of the - // capability-verified additions. The capability delta is cumulative - // since the committed close; the parse key below keeps only this - // stage's exact suffix. - let mut segment = Vec::with_capacity(appended.len()); - for (module, file_id) in &appended { - let source = snapshot - .source_id(*file_id) - .expect("the appended source has a stable content identity") - .clone(); - let Ok(index) = delta.binary_search_by(|revision| revision.module.cmp(module)) else { - return Err(reject( - "an appended module is outside the capability-verified delta", - )); - }; - if delta[index].source != source { - return Err(reject( - "an appended module's source differs from the capability-verified delta", - )); - } - segment.push(crate::ModuleRevision { - module: module.clone(), - source, - }); - } - segment.sort_by(|left, right| left.module.cmp(&right.module)); - Ok(PreparedSuccessorParse { - predecessor_program, - predecessor_order, - predecessor_revision, - predecessor_terminal, - appended, - segment: segment.into(), - }) - } - - /// The successor parse projection (RUE-1112): keyed on the published - /// lineage identity plus the exact appended segment, parsing ONLY the - /// appended modules and structurally extending the retained predecessor - /// parsed program and presentation order. - #[allow(clippy::type_complexity)] - fn execute_parse_query_successor( - &mut self, - snapshot: &SourceSnapshot, - revision: crate::ImportInputRevision, - prepared: PreparedSuccessorParse, - attempt_id: AttemptId, - ) -> ( - ParseQueryRecord, - Arc, - QueryAttemptExecution, - ParsedModulesWork, - ParseInvalidationSummary, - ) { - let PreparedSuccessorParse { - predecessor_program, - predecessor_order, - predecessor_revision, - predecessor_terminal, - appended, - segment, - } = prepared; - let successor_order = crate::shared_segments::SharedList::extend( - &predecessor_order, - appended.iter().map(|(module, _)| module.clone()).collect(), - ); - self.select_diagnostic_presentation(Some(successor_order.clone())); - let presentation = DiagnosticAttemptProvenance::Presentation(successor_order); - - // A successor key embeds only the published lineage identity and its - // appended segment. - self.parse_key_entries_compared = self - .parse_key_entries_compared - .saturating_add(segment.len() as u64); - let key = ParseQueryKey::Successor { - revision, - segment, - predecessor: predecessor_revision, - }; - let runtime_revision = - // The runtime revision's compatibility slot is the observation - // regime, not the per-request counter (RUE-1137). This must match - // how import publication built the revision, or the module-input - // and parse projections cannot find their published views. - rue_query::Revision::new(revision.revision_id, revision.compatibility_token); - self.parse_modules_dispatched = self - .parse_modules_dispatched - .saturating_add(appended.len() as u64); - let (modular_result, modular_work) = self.queries.revisioned.parse_program_extension( - runtime_revision, - &predecessor_program, - &appended, - ); - self.parse_invalidation_entries_compared = self - .parse_invalidation_entries_compared - .saturating_add(appended.len() as u64); - let appended_modules: Vec = - appended.iter().map(|(module, _)| module.clone()).collect(); - let parse_family = self.queries.revisioned.parse.family_handle(); - let prepared = self.queries.revisioned.parse.prepare(key.clone()); - let attempt = prepared.execute(runtime_revision, attempt_id, |context| { - // The record adopts the CAPTURED predecessor parse terminal as a - // runtime dependency — the exact terminal held by preparation, - // observed by node, incarnation, and stamp with no key hash or - // content comparison — so successor-after-predecessor is a real - // query edge: red/green validation and leases flow through it, - // and the node's endorsement at this revision carries the exact - // stamp to every compatible descendant. Adoption is sound here - // because parse keys are content-addressed: the key alone pins - // the terminal's value. A stale or evicted terminal aborts the - // attempt rather than being silently re-derived. - if parse_family - .observe_adopted_terminal(context, &predecessor_terminal) - .is_err() - { - return Err(rue_query::QueryAbort::Canceled); - } - // Plus exactly the appended modules' input leaves; the remaining - // predecessor content is pinned by the dependency above and the - // published lineage identity in the key. - for (module, _) in &appended { - context.input( - crate::revisioned_query_database::RevisionedQueryDatabase::module_source_input( - module, - ), - )?; - } - let work = modular_work; - let invalidation = - crate::parsed_modules::classify_successor_invalidation(&appended_modules); - let result = modular_result; - // Freeze diagnostics privately with the query output. Session - // selection and metrics happen only after atomic publication. - let diagnostics = Arc::new(FrontendDiagnosticSnapshot { - source: snapshot.clone(), - stage: FrontendDiagnosticIdentity::Syntax, - provenance: presentation.clone(), - errors: result - .as_ref() - .err() - .map_or_else(|| Arc::from([]), |errors| errors.as_slice().to_vec().into()), - warnings: Arc::from([]), - }); - Ok(ParseQueryRecord { - key, - runtime_revision, - snapshot: snapshot.clone(), - result, - diagnostics, - work, - invalidation, - }) - }); - self.queries.revisioned.select_parse(&attempt); - let terminal = attempt - .terminal() - .unwrap_or_else(|| panic!("parse query aborted: {:?}", attempt.abort())); - let record = match terminal.outcome() { - rue_query::QueryOutcome::Success(record) => record.clone(), - rue_query::QueryOutcome::Failure(_) => unreachable!("parse retains typed records"), - }; - let execution = match attempt.execution() { - rue_query::RequestExecution::Computed => { - self.metrics - .diagnostic_publication(self.diagnostics.latest().is_some()); - QueryAttemptExecution::Computed - } - rue_query::RequestExecution::Reused | rue_query::RequestExecution::Joined => { - self.reuse_diagnostics(record.diagnostics.clone()); - QueryAttemptExecution::Reused - } - rue_query::RequestExecution::Aborted => unreachable!(), - }; - let work = if execution == QueryAttemptExecution::Computed { - record.work - } else { - ParsedModulesWork::default() - }; - // A successor record's classification is relative to the retained - // predecessor its key pins, so the reused branch reuses it verbatim. - let invalidation = record.invalidation.clone(); - let view = self.queries.revisioned.parse.attempt_view( - attempt_id, - attempt, - QueryStructuralWork::Parse(work), - ); - self.diagnostics.select(view.clone()); - (record, view, execution, work, invalidation) - } - - fn parse_staging_snapshot( - &mut self, - snapshot: &SourceSnapshot, - successor: Option<(crate::ImportInputRevision, &Arc<[crate::ModuleRevision]>)>, - ) -> ( - Result, CompileErrors>, - ParsedModulesWork, - Option, - ) { - // A successor stage MUST extend its verified predecessor: a failed - // predecessor binding rejects the stage rather than silently falling - // back to a full content-keyed build under successor authority. - let prepared_successor = match successor { - Some((revision, delta)) => match self.prepare_successor_parse(snapshot, delta) { - Ok(prepared) => Some((revision, prepared)), - Err(errors) => return (Err(errors), ParsedModulesWork::default(), None), - }, - None => None, - }; - let staged_successor = prepared_successor.is_some(); - let mut guard = self.metrics.begin_unprojected("parse"); - let attempt_id = guard.id; - let (record, view, execution, work, _invalidation) = match prepared_successor { - Some((revision, prepared)) => { - self.execute_parse_query_successor(snapshot, revision, prepared, attempt_id) - } - None => { - let order = snapshot - .files() - .map(|source| snapshot.module_id(source.file_id).unwrap().clone()) - .collect::>(); - self.parse_sources_materialized = self - .parse_sources_materialized - .saturating_add(order.len() as u64); - let order = crate::shared_segments::SharedList::flat(order.into()); - self.select_diagnostic_presentation(Some(order.clone())); - let presentation = DiagnosticAttemptProvenance::Presentation(order); - self.execute_parse_query(snapshot, presentation, attempt_id) - } - }; - guard.started(); - let result = record.result.clone(); - guard.attach_diagnostics(record.diagnostics.clone()); - guard.bind(view); - guard.finish(execution, None, &result, QueryStructuralWork::None); - self.metrics.synchronize(); - let retained = staged_successor.then(|| record.clone()); - (result, work, retained) - } - - fn select_import_plan_query( - &mut self, - key: ImportPlanQueryKey, - ) -> (crate::query_graph::ObservedDependency, bool) { - let dependency = self - .queries - .import_plan_inputs - .publish_retained(&mut self.queries.graph, key.clone()); - let attempt_id = self.metrics.allocate_attempt_id(); - let reused = self - .queries - .import_plans - .request_selected(&mut self.queries.graph, key, attempt_id.0) - .unwrap_or_else(query_control_error); - if let Some((_, handle)) = &reused { - self.diagnostics.select(handle.as_view()); - } - let publish = reused.is_none(); - (dependency, publish) - } - - fn publish_import_plan_query( - &mut self, - key: ImportPlanQueryKey, - result: Result, - diagnostics: Arc, - attempted_artifact: Option>, - dependency: crate::query_graph::ObservedDependency, - publish: bool, - ) { - if publish { - let handle = self - .queries - .import_plans - .publish_selected( - &mut self.queries.graph, - ImportPlanQueryRecord { - key, - result, - diagnostics, - attempted_artifact, - }, - [dependency], - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - } - } - - fn select_import_closure_query( - &mut self, - open: &ImportDiscoveryRevisionArtifact, - plan: &crate::ImportDiscoveryPlan, - ledger: &crate::ImportObservationLedger, - successor: Option<&(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, - ) -> ( - ImportClosureQueryKey, - Vec, - bool, - ) { - // Reconstruct the exact key the stage published its plan terminal under: - // the successor identity key for a successor close, the content key - // otherwise. - let plan_key = match successor { - Some((revision, delta)) => ImportPlanQueryKey::Successor { - revision: *revision, - delta: delta.clone(), - policy_version: crate::IMPORT_DISCOVERY_POLICY_VERSION, - }, - None => ImportPlanQueryKey::Ordinary(Box::new(OrdinaryImportPlanKey { - source: ExactSourceInput::new(&open.snapshot), - context: open.context.clone(), - policy_version: crate::IMPORT_DISCOVERY_POLICY_VERSION, - accepted_reads: open.accepted_reads.clone(), - carried_ledger: open.ledger.clone(), - })), - }; - let plan_dependency = self - .queries - .import_plans - .handle(&plan_key) - .expect("an open discovery revision retains its typed plan terminal") - .observed(); - let key = match successor { - Some((revision, delta)) => ImportClosureQueryKey::Successor { - revision: *revision, - delta: delta.clone(), - policy_version: crate::IMPORT_DISCOVERY_POLICY_VERSION, - }, - None => ImportClosureQueryKey::Ordinary(Box::new(OrdinaryImportClosureKey { - source: ExactSourceInput::new(&open.snapshot), - context: open.context.clone(), - policy_version: crate::IMPORT_DISCOVERY_POLICY_VERSION, - accepted_reads: open.accepted_reads.clone(), - plan: plan.clone(), - ledger: ledger.clone(), - })), - }; - let input_dependency = self - .queries - .import_closure_inputs - .publish_retained(&mut self.queries.graph, key.clone()); - let attempt_id = self.metrics.allocate_attempt_id(); - let reused = self - .queries - .import_closures - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .unwrap_or_else(query_control_error); - if let Some((_, handle)) = &reused { - self.diagnostics.select(handle.as_view()); - } - let publish = reused.is_none(); - (key, vec![plan_dependency, input_dependency], publish) - } - - fn publish_import_closure_query( - &mut self, - key: ImportClosureQueryKey, - result: Result, CompileErrors>, - artifact: Arc, - dependencies: Vec, - publish: bool, - ) { - if publish { - let diagnostics = artifact - .diagnostic_snapshot - .as_ref() - .expect("closed discovery terminals retain diagnostics") - .clone(); - let handle = self - .queries - .import_closures - .publish_selected( - &mut self.queries.graph, - ImportClosureQueryRecord { - key, - result, - artifact, - diagnostics, - }, - dependencies, - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - } - } - - fn run_parse_update( - &mut self, - snapshot: &SourceSnapshot, - presentation: DiagnosticAttemptProvenance, - ) -> CompilerSessionUpdate { - let mut guard = self.metrics.begin_unprojected("parse"); - let attempt_id = guard.id; - let (record, view, execution, parse_work, invalidation) = - self.execute_parse_query(snapshot, presentation, attempt_id); - guard.started(); - self.metrics.update(parse_work, invalidation.clone()); - let result = record.result.clone(); - let diagnostics = record.diagnostics.clone(); - guard.attach_diagnostics(diagnostics.clone()); - guard.bind(view); - guard.finish(execution, None, &result, QueryStructuralWork::None); - self.metrics.synchronize(); - match result { - Ok(candidate) => { - if self.open_discovery.as_deref().is_some_and(|artifact| { - artifact.source_revision != *candidate.source_revision() - }) { - self.open_discovery = None; - } - let exact = self.published.as_deref().is_some_and(|published| { - programs_are_pointer_equivalent(published, &candidate) - }); - let downstream_invalidated = self.published.is_some() && !exact; - if exact { - let changed = self.queries.publish_source(ExactSourceInput::new(snapshot)); - debug_assert!(!changed); - self.published_snapshot = Some(snapshot.clone()); - CompilerSessionUpdate { - result: Ok(self.published.as_ref().unwrap().clone()), - work: parse_work, - #[cfg(test)] - invalidation, - downstream_invalidated: false, - diagnostics, - } - } else { - self.queries.publish_source(ExactSourceInput::new(snapshot)); - self.metrics.project_dependency_invalidations( - &self.queries.graph, - downstream_invalidated, - ); - self.published = Some(candidate.clone()); - self.published_snapshot = Some(snapshot.clone()); - self.refresh_retention_metrics(); - CompilerSessionUpdate { - result: Ok(candidate), - work: parse_work, - #[cfg(test)] - invalidation, - downstream_invalidated, - diagnostics, - } - } - } - Err(errors) => CompilerSessionUpdate { - result: Err(errors), - work: parse_work, - #[cfg(test)] - invalidation, - downstream_invalidated: false, - diagnostics, - }, - } - } - - /// The successor-close counterpart of [`Self::run_parse_update`]: adopts - /// the successor parse terminal for semantic queries with the same - /// publication bookkeeping, without re-running the whole-program - /// content-keyed projection (RUE-1112). The candidate extends the retained - /// predecessor by construction, so downstream invalidation follows from an - /// existing publication rather than a module-table comparison. - fn run_parse_update_successor( - &mut self, - snapshot: &SourceSnapshot, - retained: ParseQueryRecord, - ) -> CompilerSessionUpdate { - let mut guard = self.metrics.begin_unprojected("parse"); - let attempt_id = guard.id; - // Re-request the exact staged terminal: same key, same revision. The - // stage's selection protects that terminal, so this reuses it without - // publishing anything new; the recompute body republishes the retained - // record verbatim only if the terminal were ever evicted. - if let ParseQueryKey::Successor { segment, .. } = &retained.key { - self.parse_key_entries_compared = self - .parse_key_entries_compared - .saturating_add(segment.len() as u64); - } - let key = retained.key.clone(); - let runtime_revision = retained.runtime_revision; - let recompute = retained.clone(); - let prepared = self.queries.revisioned.parse.prepare(key); - let attempt = prepared.execute(runtime_revision, attempt_id, |context| { - for module in &recompute.invalidation.added { - context.input( - crate::revisioned_query_database::RevisionedQueryDatabase::module_source_input( - module, - ), - )?; - } - Ok(recompute.clone()) - }); - self.queries.revisioned.select_parse(&attempt); - let terminal = attempt - .terminal() - .unwrap_or_else(|| panic!("parse query aborted: {:?}", attempt.abort())); - let record = match terminal.outcome() { - rue_query::QueryOutcome::Success(record) => record.clone(), - rue_query::QueryOutcome::Failure(_) => unreachable!("parse retains typed records"), - }; - let execution = match attempt.execution() { - rue_query::RequestExecution::Computed => QueryAttemptExecution::Computed, - rue_query::RequestExecution::Reused | rue_query::RequestExecution::Joined => { - self.reuse_diagnostics(record.diagnostics.clone()); - QueryAttemptExecution::Reused - } - rue_query::RequestExecution::Aborted => unreachable!(), - }; - // The stage already accounted this terminal's parse work; re-selecting - // it at close performs none. - let parse_work = ParsedModulesWork::default(); - let invalidation = record.invalidation.clone(); - let view = self.queries.revisioned.parse.attempt_view( - attempt_id, - attempt, - QueryStructuralWork::Parse(parse_work), - ); - self.diagnostics.select(view.clone()); - guard.started(); - self.metrics.update(parse_work, invalidation.clone()); - let result = record.result.clone(); - let diagnostics = record.diagnostics.clone(); - guard.attach_diagnostics(diagnostics.clone()); - guard.bind(view); - guard.finish(execution, None, &result, QueryStructuralWork::None); - self.metrics.synchronize(); - match result { - Ok(candidate) => { - if self.open_discovery.as_deref().is_some_and(|artifact| { - artifact.source_revision != *candidate.source_revision() - }) { - self.open_discovery = None; - } - let downstream_invalidated = self.published.is_some(); - // The predecessor source leaf stays live: additive adoption - // must not disappear it and transitively invalidate every - // retained terminal that still correctly depends on it. - self.queries - .publish_source_additive(ExactSourceInput::new(snapshot)); - self.metrics - .project_dependency_invalidations(&self.queries.graph, downstream_invalidated); - self.published = Some(candidate.clone()); - self.published_snapshot = Some(snapshot.clone()); - self.refresh_retention_metrics(); - CompilerSessionUpdate { - result: Ok(candidate), - work: parse_work, - #[cfg(test)] - invalidation, - downstream_invalidated, - diagnostics, - } - } - Err(errors) => CompilerSessionUpdate { - result: Err(errors), - work: parse_work, - #[cfg(test)] - invalidation, - downstream_invalidated: false, - diagnostics, - }, - } - } - - /// Return the graph adopted by import discovery. - /// - /// Import-free direct sessions synthesize the uniquely valid empty graph; - /// import-bearing sessions never reconstruct resolution from loaded paths. - pub fn import_graph( - &mut self, - std_dir: Option<&str>, - ) -> Result, CompileErrors> { - let mut guard = self.metrics.begin::(); - let mut execution = QueryAttemptExecution::Rejected; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.import_graph_attempt(std_dir, &mut guard, &mut execution) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - if let Ok(output) = &result { - self.queries.publish_import_graph(output.graph().clone()); - } - if execution == QueryAttemptExecution::Reused { - execution = QueryAttemptExecution::Adopted; - } - guard.finish(execution, None, &result, QueryStructuralWork::None); - self.metrics.synchronize(); - result - } - - fn import_graph_attempt( - &mut self, - std_dir: Option<&str>, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - ) -> Result, CompileErrors> { - self.require_closed_discovery()?; - if let Some(committed) = self.committed_import_discovery_artifact() { - let graph = committed - .graph() - .expect("closed-valid discovery revisions retain their canonical graph"); - if graph.input().std_dir.as_deref() != std_dir { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "the requested standard-library context differs from the committed import discovery revision" - .into(), - ), - ))); - } - *execution = QueryAttemptExecution::Reused; - return Ok(graph.clone()); - } - let parsed = self.published.clone().ok_or_else(no_published_program)?; - if !parsed.import_directives().is_empty() { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "import-bearing revisions require a committed discovery graph".into(), - ), - ))); - } - let resolution = ModuleResolutionInputs::new( - parsed.root().clone(), - parsed - .modules() - .iter() - .map(|module| crate::ModuleResolutionInput { - module: module.module_id().clone(), - physical_path: Arc::from(module.physical_path()), - }) - .collect(), - ) - .expect("published parsed modules have validated resolution inputs"); - let input = ImportGraphInputDescriptor { - sources: parsed.source_revision().clone(), - resolution, - std_dir: std_dir.map(Arc::from), - }; - *execution = QueryAttemptExecution::Computed; - guard.started(); - let graph = crate::import_graph::import_free_canonical_graph(parsed.as_ref())?; - let validation = validate_canonical_import_graph(&graph, &input.resolution); - Ok(Arc::new(CanonicalImportGraphOutput { - input, - graph, - validation, - })) - } - - pub(crate) fn merge(&mut self) -> Result, CompileErrors> { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let mut execution = QueryAttemptExecution::Rejected; - let mut origin = None; - let mut attempt_work = None; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.merge_attempt( - attempt_id, - &mut guard, - &mut execution, - &mut origin, - &mut attempt_work, - ) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - let structural = attempt_work - .map(QueryStructuralWork::Merge) - .unwrap_or(QueryStructuralWork::None); - if guard.cancel_requested { - let key = self - .queries - .merge - .computing_key() - .expect("canceled merge retains its computing key"); - let attempt = self.queries.merge.record_aborted_attempt( - &mut self.queries.graph, - key, - attempt_id.0, - AbortedQueryReason::Canceled, - structural.clone(), - guard.dependencies.clone(), - guard.diagnostics.clone(), - ); - guard.bind(attempt); - } - guard.finish(execution, origin, &result, structural); - self.metrics.synchronize(); - result - } - - fn merge_attempt( - &mut self, - attempt_id: AttemptId, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - origin: &mut Option, - attempt_work: &mut Option, - ) -> Result, CompileErrors> { - self.require_closed_discovery()?; - let parsed = self.published.clone().ok_or_else(no_published_program)?; - let key = MergeQueryKey { - source: ExactSourceInput::new( - self.published_snapshot - .as_ref() - .expect("a published parsed program retains its exact source snapshot"), - ), - presentation: self - .batch_diagnostic_order - .as_ref() - .map(crate::shared_segments::SharedList::as_arc), - }; - if let Some((entry, handle)) = self - .queries - .merge - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .unwrap_or_else(query_control_error) - { - let result = entry.result.clone(); - let diagnostics = entry.diagnostics.clone(); - let cached_origin = handle.origin_attempt_id(); - *execution = QueryAttemptExecution::Reused; - *origin = Some(cached_origin); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - guard.observe( - self.queries - .graph - .direct_dependencies::(handle.observed().node), - ); - guard.attach_diagnostics(diagnostics.clone()); - self.reuse_diagnostics(diagnostics); - return result; - } - let source_dependency = self - .queries - .source_inputs - .selected(&self.queries.graph) - .expect("merge retains its exact source input"); - // The runtime parse terminal already validated this exact leaf. Merge - // remains on the compatibility projection until its family migrates. - guard.observe([source_dependency]); - if let Some(entry) = self.queries.merge.publish_selected_equivalent( - &mut self.queries.graph, - key.clone(), - &key.source.revision, - [source_dependency], - ) { - *execution = QueryAttemptExecution::Reused; - *origin = Some( - self.queries - .merge - .handle(&key) - .expect("equivalent merge publication retains its origin") - .origin_attempt_id(), - ); - let handle = self - .queries - .merge - .handle(&key) - .expect("equivalent merge publication retains its attempt"); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - guard.observe([source_dependency]); - guard.attach_diagnostics(entry.diagnostics.clone()); - self.reuse_diagnostics(entry.diagnostics.clone()); - return entry.result; - } - *execution = QueryAttemptExecution::Computed; - guard.started(); - let runtime_revision = self - .queries - .revisioned - .parse - .last_good_record() - .expect("merge has a successful parse terminal") - .runtime_revision; - let projected_indexes = { - let _span = tracing::info_span!("module_index_projection").entered(); - self.queries - .revisioned - .projected_module_indexes(runtime_revision, &parsed) - }; - // Freeze the traversal work before the fallible duplicate/definition - // checks so deterministic merge failures retain the work already done. - *attempt_work = Some(CanonicalMergeWork { - modules_visited: parsed.modules().len(), - items_visited: parsed - .modules() - .iter() - .map(|module| module.ast().items.len()) - .sum(), - candidates_visited: projected_indexes.as_ref().map_or(0, |indexes| { - indexes.iter().map(|index| index.definitions.len()).sum() - }), - ..CanonicalMergeWork::default() - }); - guard.accrue(QueryStructuralWork::Merge( - attempt_work.expect("merge prefix just installed"), - )); - let batch_order = self - .batch_diagnostic_order - .as_ref() - .map(crate::shared_segments::SharedList::as_arc); - let merged = { - let _span = tracing::info_span!("canonical_merge").entered(); - projected_indexes - .and_then(|indexes| { - merge_parsed_modules_reusing_indexes( - &parsed, - &indexes, - self.definition_shard_baseline.as_ref(), - batch_order.as_deref(), - ) - }) - .map(Arc::new) - }; - if self.cancel_merge_at_commit_boundary() { - guard.request_cancel(); - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput("merge query canceled before commit".into()), - ))); - } - if let Ok(merged) = &merged { - debug_assert_eq!(merged.ast().source_revision(), parsed.source_revision()); - *attempt_work = Some(merged.work()); - guard.accrue(QueryStructuralWork::Merge(merged.work())); - } - if let Ok(merged) = &merged { - self.definition_shard_baseline = Some(merged.definitions().clone()); - } - let source = self - .published_snapshot - .clone() - .expect("published program retains source snapshot"); - let diagnostics = self.publish_diagnostics( - &source, - FrontendDiagnosticIdentity::Merge, - merged.as_ref().err(), - &[], - ); - guard.attach_diagnostics(diagnostics.clone()); - let handle = self - .queries - .merge - .publish_selected_attempt( - &mut self.queries.graph, - MergeCacheEntry { - key, - result: merged.clone(), - diagnostics, - }, - [source_dependency], - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - merged - } - - /// Query the canonical RIR through an immutable, owner-retaining view. - pub fn rir(&mut self) -> Result, CompileErrors> { - self.canonical_rir().map(crate::RirView::new).map(Arc::new) - } - - pub(crate) fn canonical_rir(&mut self) -> Result, CompileErrors> { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let mut execution = QueryAttemptExecution::Rejected; - let mut origin = None; - let mut attempt_work = None; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.rir_attempt( - attempt_id, - &mut guard, - &mut execution, - &mut origin, - &mut attempt_work, - ) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - let structural = attempt_work - .map(QueryStructuralWork::Rir) - .unwrap_or(QueryStructuralWork::None); - guard.finish(execution, origin, &result, structural); - self.metrics.synchronize(); - result - } - - pub(crate) fn selected_semantic_rir_owner(&self) -> Option> { - self.queries - .semantic - .selected_record(&self.queries.graph) - .and_then(|record| record.rir.clone()) - } - - fn rir_attempt( - &mut self, - attempt_id: AttemptId, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - origin: &mut Option, - attempt_work: &mut Option, - ) -> Result, CompileErrors> { - self.require_successful_import_diagnostics()?; - let source = self - .published - .as_ref() - .ok_or_else(no_published_program)? - .source_revision() - .clone(); - let key = RirQueryKey { - source: source.clone(), - }; - if let Some((cached, handle)) = self - .queries - .rir - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .unwrap_or_else(query_control_error) - { - *execution = QueryAttemptExecution::Reused; - *origin = Some(handle.origin_attempt_id()); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - guard.observe( - self.queries - .graph - .direct_dependencies::(handle.observed().node), - ); - guard.attach_diagnostics(cached.diagnostics.clone()); - self.reuse_diagnostics(cached.diagnostics.clone()); - return cached.result; - } - let merged = self.merge(); - let result = match &merged { - Ok(merged) => { - *execution = QueryAttemptExecution::Computed; - guard.started(); - let revision = self - .queries - .revisioned - .parse - .last_good_record() - .expect("published syntax has a successful parse terminal") - .runtime_revision; - let module_ids = merged - .ast() - .modules() - .iter() - .map(|module| module.module_id().clone()) - .collect::>(); - let (module_rirs, query_work) = { - let _span = tracing::info_span!("module_rir_lowering").entered(); - self.queries.revisioned.module_rirs(revision, module_ids) - }; - match module_rirs { - Ok(modules) => { - let projected = { - let _span = tracing::info_span!("rir_projection").entered(); - project_module_rirs_with_work(merged, &modules, query_work) - }; - match projected { - Ok(rir) => { - let rir = Arc::new(rir); - *attempt_work = Some(rir.work()); - guard.accrue(QueryStructuralWork::Rir(rir.work())); - Ok(rir) - } - Err((error, work)) => { - *attempt_work = Some(work); - guard.accrue(QueryStructuralWork::Rir(work)); - Err(CompileErrors::from(error)) - } - } - } - Err(errors) => { - *attempt_work = Some(query_work); - guard.accrue(QueryStructuralWork::Rir(query_work)); - Err(errors) - } - } - } - Err(errors) => Err(errors.clone()), - }; - if let Ok(rir) = &result { - debug_assert_eq!(rir.source_revision(), &source); - } - let source_snapshot = self - .published_snapshot - .clone() - .expect("RIR query retains its exact source snapshot"); - let diagnostics = Arc::new(FrontendDiagnosticSnapshot { - source: source_snapshot, - stage: FrontendDiagnosticIdentity::Rir(source.clone()), - provenance: DiagnosticAttemptProvenance::Canonical, - errors: result - .as_ref() - .err() - .map_or_else(|| Arc::from([]), |errors| errors.as_slice().to_vec().into()), - warnings: Arc::from([]), - }); - let merge_dependency = self - .queries - .merge - .selected_handle(&self.queries.graph) - .expect("RIR query observes the current merge terminal") - .observed(); - guard.observe([merge_dependency]); - guard.attach_diagnostics(diagnostics.clone()); - let handle = self - .queries - .rir - .publish_selected_attempt( - &mut self.queries.graph, - RirCacheEntry { - key, - result: result.clone(), - merged: merged.ok(), - diagnostics, - }, - [merge_dependency], - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - result - } - - /// Analyze the current published revision without issuing stable definition IDs. - /// Query semantic analysis and optimized CFGs through immutable views. - pub fn semantic( - &mut self, - options: &CompileOptions, - ) -> Result, CompileErrors> { - let owner = self.canonical_semantic(options)?; - let rir = self - .selected_semantic_rir_owner() - .expect("successful semantic query retains its exact RIR terminal"); - Ok(Arc::new(crate::SemanticView::new(owner, rir))) - } - - pub(crate) fn canonical_semantic( - &mut self, - options: &CompileOptions, - ) -> Result, CompileErrors> { - match self - .canonical_semantic_with_cancellation(options, rue_query::CancellationToken::new()) - { - Ok(output) => Ok(output), - Err(SemanticRequestControl::Compile(errors)) => Err(errors), - // `canonical_semantic` is a stable no-filesystem entry: it cannot - // acquire trusted toolchain modules, so it converts an unsatisfied - // park to its own error result at this outer boundary (RUE-1112). - // The park-aware host driver uses `semantic_or_toolchain_park`, which - // surfaces the park distinctly and retries after acquisition. - Err(SemanticRequestControl::Parked(park)) => { - Err(unresolved_toolchain_park_errors(&park)) - } - Err(SemanticRequestControl::Abort(abort)) => { - panic!("uncanceled semantic request aborted: {abort:?}") - } - } - } - - /// Collect the canonical per-function backend terminals for one semantic - /// result. This is deliberately a deterministic adapter: `CodegenUnit` - /// owns lowering, allocation, scheduling, emission, and requested - /// presentation projections; callers only order and project terminals. - pub(crate) fn codegen_products( - &mut self, - semantic: &crate::CanonicalSemanticOutput, - foreign_symbols: &[String], - options: &crate::CompileOptions, - request: rue_codegen::BackendArtifactRequest, - ) -> Result, crate::CompileErrors> { - Ok(self - .codegen_units(semantic, foreign_symbols, options, request)? - .into_iter() - .map(|collected| collected.unit.backend_product()) - .collect()) - } - - /// Collect reached canonical codegen terminals without immediately - /// collapsing them into the historical backend-product representation. - /// Object and link consumers aggregate this exact result in a - /// `ProgramImagePlan`; presentation consumers may still use the thin - /// `codegen_products` projection above. - pub(crate) fn codegen_units( - &mut self, - semantic: &crate::CanonicalSemanticOutput, - foreign_symbols: &[String], - options: &crate::CompileOptions, - request: rue_codegen::BackendArtifactRequest, - ) -> Result, crate::CompileErrors> { - let revision = self - .queries - .revisioned - .current_semantic_revision() - .ok_or_else(|| { - crate::CompileErrors::from(crate::CompileError::without_span( - crate::ErrorKind::InvalidCompilerInput( - "code generation requires a published semantic revision".into(), - ), - )) - })?; - let mappings = Arc::new(crate::backend::foreign_call_symbol_mappings( - semantic.functions(), - foreign_symbols, - )); - let foreign: Arc> = - Arc::new(foreign_symbols.iter().cloned().collect()); - let strings: Arc<[String]> = semantic.strings().to_vec().into(); - let interner = self - .selected_semantic_rir_owner() - .expect("semantic output retains the exact RIR interner") - .semantic_symbols() - .shared_interner(); - let mut units = Vec::with_capacity(semantic.functions().len()); - #[cfg(test)] - self.codegen_executions.clear(); - for function in semantic.functions() { - let attempt = self - .queries - .revisioned - .codegen_unit( - revision, - function.clone(), - semantic.type_pool().clone(), - strings.clone(), - interner.clone(), - mappings.clone(), - foreign.clone(), - function.optimized_cfg_key.clone(), - options.target, - request, - options.opt_level, - rue_query::CancellationToken::new(), - ) - .map_err(|abort| { - crate::CompileErrors::from(crate::CompileError::without_span( - crate::ErrorKind::InternalError(format!( - "codegen query aborted: {abort:?}" - )), - )) - })?; - #[cfg(test)] - self.codegen_executions - .push((function.semantic_identity.clone(), attempt.execution())); - let terminal = attempt.into_result().map_err(|abort| { - crate::CompileErrors::from(crate::CompileError::without_span( - crate::ErrorKind::InternalError(format!("codegen query aborted: {abort:?}")), - )) - })?; - let rue_query::QueryOutcome::Success(unit) = terminal.outcome() else { - unreachable!("CodegenUnit publishes typed terminals") - }; - match unit { - crate::codegen_query::CodegenUnitValue::Available(unit) => { - units.push(crate::codegen_query::CollectedCodegenUnit { - function: function.clone(), - unit: unit.clone(), - }); - } - crate::codegen_query::CodegenUnitValue::Failure(errors) => { - return Err(errors.clone()); - } - } - } - Ok(units) - } - - #[cfg(test)] - pub(crate) fn codegen_executions( - &self, - ) -> &[(crate::FunctionInstanceKey, rue_query::RequestExecution)] { - &self.codegen_executions - } - - /// Analyze the current revision, surfacing an unsatisfied trusted-toolchain - /// park distinctly instead of converting it to an error (RUE-1112). This - /// is the rooted, park-aware entry the host compile driver retries: on - /// [`SemanticParkOutcome::Parked`] it acquires exactly the demanded modules, - /// publishes a successor, and calls this again. - pub(crate) fn semantic_or_toolchain_park( - &mut self, - options: &CompileOptions, - ) -> SemanticParkOutcome { - match self - .canonical_semantic_with_cancellation(options, rue_query::CancellationToken::new()) - { - Ok(owner) => { - let rir = self - .selected_semantic_rir_owner() - .expect("successful semantic query retains its exact RIR terminal"); - SemanticParkOutcome::Ready(Arc::new(crate::SemanticView::new(owner, rir))) - } - Err(SemanticRequestControl::Compile(errors)) => SemanticParkOutcome::Errors(errors), - Err(SemanticRequestControl::Parked(park)) => { - // Atomically attach this rooted park's exact sorted missing-demand - // set to the outstanding closed continuation, making it authorizing - // (RUE-1112). Demand authority lives only here — bound to this - // closed revision and this park — so a later, non-parking close can - // never inherit it, and `publish_trusted_toolchain_successor` can - // require the successor's added set to EQUAL exactly this set. - if let Some(state) = self.continuation.as_mut() { - let mut demands = park.demands().to_vec(); - demands.sort(); - demands.dedup(); - state.attached_demands = Some(Arc::from(demands)); - } - SemanticParkOutcome::Parked(park) - } - Err(SemanticRequestControl::Abort(abort)) => { - panic!("uncanceled semantic request aborted: {abort:?}") - } - } - } - - fn canonical_semantic_with_cancellation( - &mut self, - options: &CompileOptions, - cancellation: rue_query::CancellationToken, - ) -> Result, SemanticRequestControl> { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let mut execution = QueryAttemptExecution::Rejected; - let mut origin = None; - let mut attempt_record = None; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.semantic_attempt( - options, - &cancellation, - attempt_id, - &mut guard, - &mut execution, - &mut origin, - &mut attempt_record, - ) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - // A trusted-toolchain park exits the attempt before the body transaction - // publishes a terminal, leaving the semantic query in-flight. Clear that - // in-flight attempt exactly as an abort would, so the host driver's retry - // (after acquiring the demanded module and re-closing on a new revision) - // begins a fresh selection instead of colliding with a stale computing - // key (RUE-1112). - if matches!( - result, - Err(SemanticRequestControl::Abort(_)) | Err(SemanticRequestControl::Parked(_)) - ) { - guard.request_cancel(); - } - let structural = attempt_record - .clone() - .map(Box::new) - .map(QueryStructuralWork::Semantic) - .unwrap_or(QueryStructuralWork::None); - if guard.cancel_requested { - if let Some(key) = self.queries.semantic.computing_key() { - let attempt = self.queries.semantic.record_aborted_attempt( - &mut self.queries.graph, - key, - attempt_id.0, - AbortedQueryReason::Canceled, - structural.clone(), - guard.dependencies.clone(), - guard.diagnostics.clone(), - ); - guard.bind(attempt); - } - } - guard.finish(execution, origin, &result, structural); - self.metrics.publish_semantic(self.queries.semantic.len()); - self.metrics.synchronize(); - result - } - - fn semantic_attempt( - &mut self, - options: &CompileOptions, - cancellation: &rue_query::CancellationToken, - attempt_id: AttemptId, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - origin: &mut Option, - attempt_record: &mut Option, - ) -> Result, SemanticRequestControl> { - if cancellation.is_canceled() { - return Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled, - )); - } - self.require_successful_import_diagnostics()?; - let imports = self.accepted_semantic_import_graph()?; - self.queries.publish_import_graph(imports.clone()); - self.queries.publish_request_inputs(options); - if cancellation.is_canceled() { - return Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled, - )); - } - let source = self - .published_snapshot - .clone() - .expect("semantic query retains its exact source snapshot"); - let input = CodegenInputDescriptor { - semantic: SemanticInputDescriptor::new( - &source, - options.target, - &options.preview_features, - ), - opt_level: options.opt_level.into(), - }; - let key = SemanticQueryKey { - input: input.clone(), - imports: imports.clone(), - }; - if let Some((entry, handle)) = self - .queries - .semantic - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .unwrap_or_else(query_control_error) - { - let result = entry.result.clone(); - *execution = QueryAttemptExecution::Reused; - *origin = Some(handle.origin_attempt_id()); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - guard.observe( - self.queries - .graph - .direct_dependencies::(handle.observed().node), - ); - guard.attach_diagnostics(entry.diagnostics.clone()); - self.reuse_diagnostics(entry.diagnostics.clone()); - if cancellation.is_canceled() { - return Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled, - )); - } - return result.map_err(SemanticRequestControl::Compile); - } - let rir_result = self.canonical_rir(); - if cancellation.is_canceled() { - return Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled, - )); - } - let rir = match rir_result { - Ok(rir) => rir, - Err(errors) => { - let record = SemanticQueryRecord { - input: input.clone(), - work: CanonicalSemanticWork::default(), - failure: None, - failed: true, - }; - guard.accrue(QueryStructuralWork::Semantic(Box::new(record.clone()))); - *attempt_record = Some(record); - let diagnostics = self.publish_diagnostics( - &source, - FrontendDiagnosticIdentity::Semantic(semantic_diagnostic_input( - &input, - imports.clone(), - )), - Some(&errors), - &[], - ); - let dependency = self - .queries - .rir - .selected_handle(&self.queries.graph) - .expect("semantic rejection observes a deterministic RIR failure") - .observed(); - guard.observe([dependency]); - guard.attach_diagnostics(diagnostics.clone()); - let handle = self - .queries - .semantic - .publish_selected_attempt( - &mut self.queries.graph, - SemanticCacheEntry { - key, - result: Err(errors.clone()), - rir: None, - diagnostics, - durable_declaration_cache: None, - oracle_injected: false, - }, - [dependency], - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - return Err(SemanticRequestControl::Compile(errors)); - } - }; - let merged = self - .queries - .rir - .selected_record(&self.queries.graph) - .and_then(|entry| entry.merged.clone()) - .expect("successful RIR terminal retains its exact merge input"); - #[cfg(test)] - if std::mem::take(&mut self.cancel_semantic_after_dependency) { - cancellation.cancel(); - } - if cancellation.is_canceled() { - return Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled, - )); - } - *execution = QueryAttemptExecution::Computed; - guard.started(); - let runtime_revision = self - .queries - .revisioned - .current_semantic_revision() - .expect("semantic preparation observes a published source/import revision"); - let declaration_shells_span = - tracing::info_span!("declaration_shells", phase = "semantic_analysis").entered(); - let query_shells = { - let _span = tracing::info_span!("declaration_shell_projection").entered(); - self.queries.revisioned.projected_declaration_shells( - runtime_revision, - merged.ast(), - cancellation.clone(), - ) - }; - let mut prepared = match query_shells { - Ok(query_shells) => { - let _span = tracing::info_span!("declaration_shell_prepare").entered(); - prepare_query_declaration_shells(&merged, &rir, options, &imports, &query_shells) - } - Err(crate::revisioned_query_database::DeclarationShellBatchFailure::Query(abort)) => { - return Err(SemanticRequestControl::Abort(abort)); - } - Err(crate::revisioned_query_database::DeclarationShellBatchFailure::Stable( - failure, - )) => Err(CanonicalSemanticFailure::declaration( - declaration_shell_failure_diagnostics(merged.ast(), &failure), - CanonicalSemanticWork::default(), - )), - }; - drop(declaration_shells_span); - let declaration_semantics = { - let _span = tracing::info_span!( - "declaration_semantics_projection", - phase = "semantic_analysis" - ) - .entered(); - self.queries.revisioned.projected_declaration_semantics( - runtime_revision, - merged.ast(), - options.target, - &options.preview_features, - cancellation.clone(), - ) - }; - let ( - query_declarations, - query_anonymous_nominals, - query_declaration_dependencies, - query_c_export_roots, - ) = match declaration_semantics { - Ok(projection) => ( - Some(projection.declarations), - projection.anonymous_nominals, - projection.dependencies, - projection.c_export_roots, - ), - Err(crate::revisioned_query_database::SemanticNucleusBatchFailure::Query(abort)) => { - return Err(SemanticRequestControl::Abort(abort)); - } - Err(crate::revisioned_query_database::SemanticNucleusBatchFailure::Stable { - declaration, - failure, - }) => { - let work = match &prepared { - Ok(prepared) => CanonicalSemanticWork { - declaration_index: prepared.declaration_index_work(), - ..CanonicalSemanticWork::default() - }, - Err(preparation_failure) => preparation_failure.failure.work, - }; - prepared = Err(CanonicalSemanticFailure::declaration( - semantic_nucleus_failure_diagnostics( - merged.ast(), - declaration.as_ref(), - &failure, - ), - work, - )); - (None, Arc::from([]), Arc::from([]), Arc::from([])) - } - }; - if let (Some(declarations), Ok(prepared_definitions)) = - (query_declarations.as_deref(), prepared.as_ref()) - && let Some(main) = prepared_definitions - .definitions() - .definitions() - .iter() - .find(|record| { - let key = record.stable_key(); - key.kind() == crate::StableDefinitionKind::Function - && key.name() == "main" - && key.module() == merged.ast().root() - }) - && let Some(declaration) = declarations - .iter() - .find(|declaration| declaration.key == *main.stable_key()) - && let crate::durable_semantics::DurableDeclarationPayload::Callable { - parameters, - result, - .. - } = &declaration.payload - { - let reason = if !parameters.is_empty() { - Some("`main` must not declare parameters") - } else if !matches!( - result, - crate::durable_semantics::DurableType::I32 - | crate::durable_semantics::DurableType::Unit - ) { - Some("`main` must return `i32` or `()`") - } else { - None - }; - if let Some(reason) = reason { - let work = CanonicalSemanticWork { - declaration_index: prepared_definitions.declaration_index_work(), - ..CanonicalSemanticWork::default() - }; - prepared = Err(CanonicalSemanticFailure::declaration( - CompileErrors::from(CompileError::new( - ErrorKind::InvalidMainSignature { reason }, - main.declaration_span(), - )), - work, - )); - } - } - let durable_body_work = crate::DurableBodyWork::default(); - let mut durable_body_candidates = Vec::new(); - let mut durable_specialized_body_candidates = Vec::new(); - let mut durable_anonymous_body_candidates = Vec::new(); - let mut queried_body_work = rue_air::BodyAnalysisWork::default(); - let mut body_produced_anonymous = BTreeMap::new(); - let mut body_consulted_anonymous = BTreeMap::new(); - let mut body_query_errors = BTreeMap::new(); - let mut body_query_reference_cache = BTreeMap::new(); - let mut demanded_drop_glue: Arc<[crate::TypeInstanceKey]> = Arc::from([]); - let mut demanded_drop_glue_plans: Arc< - [(crate::TypeInstanceKey, crate::type_queries::DropGlueFacts)], - > = Arc::from([]); - // RUE-1027 production boundary: derive the reached callable frontier - // serially from the references owned by each body transaction. Ordinary - // call recursion is worklist state, never a query dependency cycle. - // RUE-1028 replaces this deterministic coordinator with database-owned - // reachability and parallel frontier scheduling. - if let Ok(prepared_definitions) = prepared.as_ref() { - let configuration = crate::semantic_query_nucleus::SemanticQueryConfiguration { - target: options.target, - preview_features: StablePreviewFeatures::new(&options.preview_features), - }; - let mut pending = std::collections::BTreeSet::new(); - for record in prepared_definitions.definitions().definitions() { - let stable = record.stable_key(); - if stable.kind() == crate::StableDefinitionKind::Function - && stable.name() == "main" - && stable.module() == merged.ast().root() - { - pending.insert(crate::FunctionInstanceKey::Definition(stable.clone())); - } - } - pending.extend( - query_c_export_roots - .iter() - .cloned() - .map(crate::FunctionInstanceKey::Definition), - ); - let roots = pending; - let closure_request = match self.queries.revisioned.body_closure( - runtime_revision, - crate::body_query::BodyClosureQueryKey { - modules: merged - .ast() - .modules() - .iter() - .map(|module| module.module_id().clone()) - .collect::>() - .into(), - roots: roots.iter().cloned().collect::>().into(), - configuration: configuration.clone(), - }, - cancellation.clone(), - ) { - Ok(request) => request, - Err(abort) => return Err(SemanticRequestControl::Abort(abort)), - }; - let closure_terminal = &closure_request.terminal; - let rue_query::QueryOutcome::Success(closure_output) = closure_terminal.outcome() - else { - unreachable!("BodyClosure publishes typed values") - }; - demanded_drop_glue = closure_output.demanded_drop_glue.clone(); - demanded_drop_glue_plans = closure_output.demanded_drop_glue_plans.clone(); - if let Some(park) = &closure_output.parked_toolchain { - return Err(SemanticRequestControl::Parked(Box::new(park.clone()))); - } - body_query_errors.extend(closure_output.scheduling_errors.iter().cloned()); - if let Some(fatal) = &closure_output.fatal { - let (instance, errors) = match fatal { - crate::body_query::BodyClosureFatal::DeclarationFailed { - declaration, - failure, - } => ( - roots.iter().next().cloned(), - semantic_nucleus_failure_diagnostics( - merged.ast(), - declaration.as_ref(), - failure, - ), - ), - crate::body_query::BodyClosureFatal::BodyAvailability { instance, detail } => ( - Some(instance.clone()), - crate::CompileErrors::from(crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "body availability was incomplete for {instance:?}: {detail}" - )), - )), - ), - crate::body_query::BodyClosureFatal::ProducerFailed { instance, failure } => ( - Some(instance.clone()), - semantic_nucleus_failure_diagnostics(merged.ast(), None, failure), - ), - crate::body_query::BodyClosureFatal::WellKnownOptionResolution { - instance, - failure, - } => ( - Some(instance.clone()), - well_known_option_resolution_diagnostics(merged.ast(), failure), - ), - crate::body_query::BodyClosureFatal::TypeQuery { ty, detail } => ( - roots.iter().next().cloned(), - crate::CompileErrors::from(crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "canonical type query failed for {ty:?}: {detail}" - )), - )), - ), - crate::body_query::BodyClosureFatal::AnonymousDigestCollision { - digest, - first, - second, - } => ( - roots.iter().next().cloned(), - crate::CompileErrors::from(crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "stable anonymous symbol digest {digest:032x} is shared by distinct \ - producer-nominal identities {first:?} and {second:?}" - )), - )), - ), - }; - if let Some(instance) = instance { - body_query_errors.insert(instance, errors); - } else { - return Err(SemanticRequestControl::Compile(errors)); - } - } - body_query_reference_cache.clear(); - for closure_body in closure_output.bodies.iter() { - let instance = &closure_body.key.instance; - let key = &closure_body.key; - let rue_query::QueryOutcome::Success(analysis) = closure_body.bundle.outcome() - else { - unreachable!("BodyAnalysisBundle publishes typed values") - }; - let computed = matches!( - closure_request.execution_for(key), - rue_query::RequestExecution::Computed - ); - let had_retained_body = closure_request.was_retained(key); - if computed { - queried_body_work.body_analyses_computed += 1; - } else { - queried_body_work.body_analyses_reused += 1; - } - let transaction = &analysis.transaction; - if computed { - let specialized = - matches!(instance, crate::FunctionInstanceKey::Specialization { .. }); - queried_body_work.bodies_attempted += 1; - if had_retained_body { - queried_body_work.body_analyses_invalidated += 1; - } - if specialized { - queried_body_work.specialized_bodies_attempted += 1; - } - match transaction { - crate::body_query::BodyTransaction::Success { body, .. } => { - queried_body_work.bodies_succeeded += 1; - let body = match body.as_ref() { - crate::body_query::CanonicalBody::Ordinary { body, .. } - | crate::body_query::CanonicalBody::Anonymous { body, .. } - | crate::body_query::CanonicalBody::Specialization { - body, .. - } => body, - }; - queried_body_work.air_instructions_produced += body.instructions.len(); - queried_body_work.local_strings_produced += body.strings.len(); - if specialized { - queried_body_work.specialized_bodies_succeeded += 1; - queried_body_work.specialized_body_exports_attempted += 1; - queried_body_work.specialized_body_exports_succeeded += 1; - queried_body_work.specialized_body_export_instructions_emitted += - body.instructions.len(); - queried_body_work.specialized_body_export_places_emitted += - body.places.len(); - queried_body_work.specialized_body_export_strings_emitted += - body.strings.len(); - } else { - queried_body_work.ordinary_body_exports_attempted += 1; - queried_body_work.ordinary_body_exports_succeeded += 1; - queried_body_work.ordinary_body_export_instructions_emitted += - body.instructions.len(); - queried_body_work.ordinary_body_export_places_emitted += - body.places.len(); - queried_body_work.ordinary_body_export_strings_emitted += - body.strings.len(); - } - } - crate::body_query::BodyTransaction::DeterministicFailure { .. } => { - queried_body_work.bodies_failed += 1; - if specialized { - queried_body_work.specialized_bodies_failed += 1; - } - } - crate::body_query::BodyTransaction::Control(_) => { - unreachable!("body closure unwraps transaction control") - } - } - } - body_query_reference_cache - .insert(instance.clone(), transaction.references().clone()); - if let crate::body_query::BodyTransaction::DeterministicFailure { errors, .. } = - transaction - { - body_query_errors.insert(instance.clone(), errors.clone()); - continue; - } - let crate::body_query::BodyTransaction::Success { - body, - consulted_anonymous_nominals, - .. - } = transaction - else { - unreachable!("body closure contains no control transactions") - }; - body_consulted_anonymous.extend( - consulted_anonymous_nominals - .0 - .iter() - .cloned() - .map(|nominal| (nominal.identity.clone(), nominal)), - ); - if let Some(crate::body_query::ProducedAnonymous::Produced(produced)) = - analysis.produced_anonymous.as_ref() - { - body_produced_anonymous.extend( - produced - .0 - .iter() - .cloned() - .map(|nominal| (nominal.identity.clone(), nominal)), - ); - } - match body.as_ref() { - crate::body_query::CanonicalBody::Ordinary { owner, body } => { - let Some(record) = - prepared_definitions.definitions().definition_by_key(owner) - else { - body_query_errors.insert( - instance.clone(), - crate::CompileErrors::from( - crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "reached body {instance:?} published an ordinary body whose owner has no issued definition record" - )), - ), - ), - ); - continue; - }; - let Some(body_span) = record.body_span() else { - body_query_errors.insert( - instance.clone(), - crate::CompileErrors::from( - crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "reached body {instance:?} published an ordinary body whose owner definition record carries no body span" - )), - ), - ), - ); - continue; - }; - durable_body_candidates.push( - crate::canonical_semantic::PreparedDurableBodyCandidate { - owner: owner.clone(), - body_span, - body: body.clone(), - }, - ); - } - crate::body_query::CanonicalBody::Anonymous { - identity, - body_anchor, - body, - } => { - let crate::FunctionInstanceKey::AnonymousMember { owner, member: _ } = - identity - else { - body_query_errors.insert( - instance.clone(), - crate::CompileErrors::from( - crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "reached body {instance:?} published an anonymous body whose identity is not an anonymous member" - )), - ), - ), - ); - continue; - }; - let crate::TypeInstanceKey::Nominal(crate::NominalInstanceKey::Anonymous( - _, - )) = owner.as_ref() - else { - body_query_errors.insert( - instance.clone(), - crate::CompileErrors::from( - crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "reached body {instance:?} published an anonymous body whose owner is not an anonymous nominal type" - )), - ), - ), - ); - continue; - }; - let Some(source) = analysis.source_locator.as_ref() else { - body_query_errors.insert( - instance.clone(), - crate::CompileErrors::from( - crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "reached body {instance:?} published an anonymous body without a current source locator" - )), - ), - ), - ); - continue; - }; - let body_span = rue_span::Span::with_file( - source.file_id, - source - .body_start - .saturating_add(body_anchor.start) - .min(source.body_end), - source - .body_start - .saturating_add(body_anchor.end) - .min(source.body_end), - ); - durable_anonymous_body_candidates.push( - crate::canonical_semantic::PreparedDurableAnonymousBodyCandidate { - identity: identity.clone(), - body_span, - body: body.clone(), - }, - ); - } - crate::body_query::CanonicalBody::Specialization { identity, body, .. } => { - let Some(record) = prepared_definitions - .definitions() - .definition_by_key(&identity.base) - else { - body_query_errors.insert( - instance.clone(), - crate::CompileErrors::from( - crate::CompileError::without_span( - rue_error::ErrorKind::InternalError(format!( - "reached body {instance:?} published a specialization body whose base has no issued definition record" - )), - ), - ), - ); - continue; - }; - durable_specialized_body_candidates.push( - crate::canonical_semantic::PreparedDurableSpecializedBodyCandidate { - instance: instance.clone(), - identity: identity.clone(), - body_span: record.declaration_span(), - body: body.clone(), - }, - ); - } - } - } - queried_body_work.closure_bodies_visited = closure_output.bodies.len(); - tracing::info!( - bodies_attempted = queried_body_work.bodies_attempted, - bodies_visited = queried_body_work.closure_bodies_visited, - closure_restarts = queried_body_work.closure_restarts, - deferred_producer_retries = queried_body_work.deferred_producer_retries, - max_specialization_depth = queried_body_work.max_specialization_depth, - "body closure complete" - ); - } - let query_anonymous_nominals = { - let mut all = query_anonymous_nominals - .iter() - .cloned() - .map(|nominal| (nominal.identity.clone(), nominal)) - .collect::>(); - all.extend(body_consulted_anonymous); - all.extend(body_produced_anonymous); - Arc::from(all.into_values().collect::>()) - }; - // Declaration payloads have one production authority: the revisioned - // semantic query nucleus. The old durable declaration cache remains a - // test-oracle fixture only; it must never select or revive a second - // declaration resolver in a real request. - let query_declarations_for_cache = query_declarations.clone(); - let analysis = prepared.and_then(|prepared| { - if !body_query_errors.is_empty() { - let errors = body_query_errors.into_values().fold( - CompileErrors::new(), - |mut all, errors| { - all.extend(errors); - all - }, - ); - let mut work = CanonicalSemanticWork { - declaration_index: prepared.declaration_index_work(), - declaration_reuse: prepared.declaration_reuse_work(), - ..CanonicalSemanticWork::default() - }; - work.accrue_body_query_work(queried_body_work); - return Err(CanonicalSemanticFailure::body(errors, work)); - } - let durable = query_declarations - .expect("successful semantic-nucleus projection publishes declaration payloads"); - let definitions = prepared.definitions().clone(); - let mut output = analyze_prepared_canonical_program_reusing_declarations( - &merged, - &rir, - options, - &imports, - prepared, - &definitions, - &durable, - &query_anonymous_nominals, - &query_declaration_dependencies, - durable_body_candidates, - durable_specialized_body_candidates, - durable_anonymous_body_candidates, - demanded_drop_glue, - demanded_drop_glue_plans, - durable_body_work, - &self.queries.revisioned, - runtime_revision, - cancellation.clone(), - )?; - output.install_body_references(body_query_reference_cache); - output.accrue_body_query_work(queried_body_work); - Ok(output) - }); - #[cfg(test)] - if std::mem::take(&mut self.cancel_semantic_before_publication) { - cancellation.cancel(); - } - if cancellation.is_canceled() { - return Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled, - )); - } - let failure = analysis.as_ref().err().map(|failure| failure.failure); - let semantic_work = analysis - .as_ref() - .map(|output| output.work()) - .unwrap_or_else(|failure| failure.failure.work); - let result = analysis.map(Arc::new).map_err(|failure| failure.errors); - let record = SemanticQueryRecord { - input: input.clone(), - work: semantic_work, - failure, - failed: result.is_err(), - }; - guard.accrue(QueryStructuralWork::Semantic(Box::new(record.clone()))); - *attempt_record = Some(record); - let published_declaration_cache = result - .is_ok() - .then_some(query_declarations_for_cache) - .flatten() - .map(|semantics| DurableDeclarationCache { semantics }); - if let Ok(output) = &result { - debug_assert_eq!(output.input(), &input); - debug_assert_eq!(semantic_work.binding.bind_invocations, 1); - debug_assert_eq!(semantic_work.manifest.build_invocations, 1); - } - let diagnostic_input = semantic_diagnostic_input(&input, imports.clone()); - let diagnostics = self.publish_diagnostics( - &source, - FrontendDiagnosticIdentity::Semantic(diagnostic_input), - result.as_ref().err(), - result - .as_ref() - .map(|output| output.warnings()) - .unwrap_or(&[]), - ); - let rir_key = RirQueryKey { - source: rir.source_revision().clone(), - }; - let rir_dependency = self - .queries - .rir - .handle(&rir_key) - .expect("semantic publication retains its RIR terminal") - .observed(); - let dependencies = [ - rir_dependency, - self.queries - .import_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .target_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .preview_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .optimization_inputs - .selected(&self.queries.graph) - .unwrap(), - ]; - guard.observe(dependencies); - guard.attach_diagnostics(diagnostics.clone()); - let handle = self - .queries - .semantic - .publish_selected_attempt( - &mut self.queries.graph, - SemanticCacheEntry { - key, - result: result.clone(), - rir: result.is_ok().then(|| rir.clone()), - diagnostics, - durable_declaration_cache: result - .is_ok() - .then_some(published_declaration_cache) - .flatten(), - oracle_injected: false, - }, - dependencies, - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - self.diagnostics.select(handle.as_view()); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - result.map_err(SemanticRequestControl::Compile) - } - - /// Issue stable definition IDs on demand for the current semantic input. - /// - /// The authoritative semantic terminal already owns the final, - /// post-classification definition universe. This projection never starts a - /// peer declaration bind or reconstructs stable IDs from shells. - pub(crate) fn stable_definitions( - &mut self, - options: &CompileOptions, - ) -> Result, CompileErrors> { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let mut execution = QueryAttemptExecution::Rejected; - let mut origin = None; - let mut attempt_record = None; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.stable_definitions_attempt( - options, - attempt_id, - &mut guard, - &mut execution, - &mut origin, - &mut attempt_record, - ) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - let structural = attempt_record - .clone() - .map(Box::new) - .map(QueryStructuralWork::Definition) - .unwrap_or(QueryStructuralWork::None); - guard.finish(execution, origin, &result, structural); - self.metrics - .publish_definition(self.queries.definitions.len()); - self.metrics.synchronize(); - result - } - - fn stable_definitions_attempt( - &mut self, - options: &CompileOptions, - attempt_id: AttemptId, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - origin: &mut Option, - attempt_record: &mut Option, - ) -> Result, CompileErrors> { - self.require_successful_import_diagnostics()?; - let imports = self.accepted_semantic_import_graph()?; - self.queries.publish_import_graph(imports.clone()); - self.queries.publish_request_inputs(options); - let snapshot = self - .published_snapshot - .clone() - .expect("definition query retains its exact source snapshot"); - let input = - SemanticInputDescriptor::new(&snapshot, options.target, &options.preview_features); - let key = DefinitionQueryKey { - input: input.clone(), - imports: imports.clone(), - }; - if let Some((entry, handle)) = self - .queries - .definitions - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .unwrap_or_else(query_control_error) - { - *execution = QueryAttemptExecution::Reused; - *origin = Some(handle.origin_attempt_id()); - guard.bind(handle.as_view()); - return entry.output.result; - } - let rir = match self.canonical_rir() { - Ok(rir) => rir, - Err(errors) => { - let record = DefinitionQueryRecord { - input: input.clone(), - binding: DeclarationBindingWork::default(), - manifest: SemanticBindingManifestWork::default(), - issuance: BoundDefinitionWork::default(), - failed: true, - }; - guard.accrue(QueryStructuralWork::Definition(Box::new(record.clone()))); - *attempt_record = Some(record); - let dependency = self - .queries - .rir - .selected_handle(&self.queries.graph) - .expect("definition rejection observes a deterministic RIR failure") - .observed(); - let handle = self - .queries - .definitions - .publish_selected_attempt( - &mut self.queries.graph, - DefinitionCacheEntry { - key: key.clone(), - output: DefinitionQueryOutput { - provenance: key, - result: Err(errors.clone()), - }, - }, - [dependency], - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - return Err(errors); - } - }; - let merged = self - .queries - .rir - .selected_record(&self.queries.graph) - .and_then(|entry| entry.merged.clone()) - .expect("successful RIR terminal retains its exact merge input"); - - let semantic_binding_key = SemanticBindingLookupKey { - input: input.clone(), - imports: imports.clone(), - }; - let cached_semantic = self - .queries - .semantic - .get_secondary_with_handle(&semantic_binding_key) - .and_then(|(entry, handle)| { - handle - .is_valid(&mut self.queries.graph) - .then(|| entry.result.clone()) - }) - .and_then(Result::ok); - let semantic = match cached_semantic { - Some(semantic) => semantic, - None => match self.canonical_semantic(options) { - Ok(semantic) => semantic, - Err(errors) => { - let dependency = self - .queries - .semantic - .selected_handle(&self.queries.graph) - .expect("definition rejection observes a deterministic semantic failure") - .observed(); - let record = DefinitionQueryRecord { - input: input.clone(), - binding: DeclarationBindingWork::default(), - manifest: SemanticBindingManifestWork::default(), - issuance: BoundDefinitionWork::default(), - failed: true, - }; - guard.accrue(QueryStructuralWork::Definition(Box::new(record.clone()))); - *attempt_record = Some(record); - let handle = self - .queries - .definitions - .publish_selected_attempt( - &mut self.queries.graph, - DefinitionCacheEntry { - key: key.clone(), - output: DefinitionQueryOutput { - provenance: key, - result: Err(errors.clone()), - }, - }, - [dependency], - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - return Err(errors); - } - }, - }; - *execution = QueryAttemptExecution::Computed; - guard.started(); - let computation = compute_stable_definitions(&merged, options, &imports, &semantic); - let result = computation.output.result.clone(); - let record = DefinitionQueryRecord { - input, - binding: computation.binding, - manifest: computation.manifest, - issuance: computation.issuance, - failed: result.is_err(), - }; - guard.accrue(QueryStructuralWork::Definition(Box::new(record.clone()))); - *attempt_record = Some(record); - let rir_key = RirQueryKey { - source: rir.source_revision().clone(), - }; - let rir_dependency = self - .queries - .rir - .handle(&rir_key) - .expect("definition publication retains its RIR terminal") - .observed(); - let semantic_dependency = self - .queries - .semantic - .get_secondary_with_handle(&semantic_binding_key) - .map(|(_, handle)| handle.observed()) - .expect("definition publication retains a successful semantic terminal"); - let dependencies = [ - rir_dependency, - semantic_dependency, - self.queries - .import_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .target_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .preview_inputs - .selected(&self.queries.graph) - .unwrap(), - ]; - let handle = self - .queries - .definitions - .publish_selected_attempt( - &mut self.queries.graph, - DefinitionCacheEntry { - key, - output: computation.output, - }, - dependencies, - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - result - } - - /// Materialize the stable semantic dependency manifest. - /// - /// The manifest contains the supported call, destructor, declaration-type, - /// type-call-head, and named-constant edge families. Per-surface completeness - /// flags and stable blockers make incomplete capture fail closed. The query - /// shares the import and stable-definition inputs and performs no additional - /// RIR traversal. - pub(crate) fn semantic_dependency_inputs( - &mut self, - options: &CompileOptions, - std_dir: Option<&str>, - ) -> Result, CompileErrors> { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let mut execution = QueryAttemptExecution::Rejected; - let mut origin = None; - let mut attempt_work = None; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.semantic_dependency_inputs_attempt( - options, - std_dir, - attempt_id, - &mut guard, - &mut execution, - &mut origin, - &mut attempt_work, - ) - })); - let result = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - if let Err(errors) = &result - && let Some(key) = self.queries.manifests.computing_key() - { - let mut dependencies = Vec::new(); - if let Some(handle) = self.queries.semantic.selected_handle(&self.queries.graph) { - dependencies.push(handle.observed()); - } - if let Some(handle) = self - .queries - .definitions - .selected_handle(&self.queries.graph) - { - dependencies.push(handle.observed()); - } - let handle = self - .queries - .manifests - .publish_selected_attempt( - &mut self.queries.graph, - DependencyManifestCacheEntry { - key, - result: Err(errors.clone()), - }, - dependencies, - guard.structural(), - execution, - ) - .unwrap_or_else(query_control_error); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - } - let structural = attempt_work - .map(Box::new) - .map(QueryStructuralWork::DependencyManifest) - .unwrap_or(QueryStructuralWork::None); - guard.finish(execution, origin, &result, structural); - self.metrics.synchronize(); - result - } - - pub fn unstable_dependency_baseline( - &mut self, - options: &CompileOptions, - std_dir: Option<&str>, - ) -> Result, CompileErrors> { - self.semantic_dependency_inputs(options, std_dir) - .map(|manifest| { - crate::unstable::DependencyBaseline::new(manifest, self.identity.clone()) - }) - .map(Arc::new) - } - - fn semantic_dependency_inputs_attempt( - &mut self, - options: &CompileOptions, - std_dir: Option<&str>, - attempt_id: AttemptId, - guard: &mut QueryComputationGuard, - execution: &mut QueryAttemptExecution, - origin: &mut Option, - attempt_work: &mut Option, - ) -> Result, CompileErrors> { - let imports = self.import_graph(std_dir)?; - let snapshot = self - .published_snapshot - .as_ref() - .expect("stable definitions retain a published source snapshot") - .clone(); - let input = - SemanticInputDescriptor::new(&snapshot, options.target, &options.preview_features); - let key = DependencyManifestQueryKey { - input: input.clone(), - imports: imports.graph().clone(), - }; - if let Some((cached, handle)) = self - .queries - .manifests - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .unwrap_or_else(query_control_error) - { - *execution = QueryAttemptExecution::Reused; - *origin = Some(handle.origin_attempt_id()); - guard.bind(handle.as_view()); - return cached.result; - } - *execution = QueryAttemptExecution::Computed; - guard.started(); - let semantic = self.canonical_semantic(options); - let definitions = self.stable_definitions(options); - let definition_universe_state = match &definitions { - Ok(_) => SemanticDefinitionUniverseState::Complete, - Err(errors) => SemanticDefinitionUniverseState::Incomplete( - SemanticDefinitionUniverseIncompleteReason::StableDefinitionsFailed( - NonEmptyDefinitionFailures::from_errors(errors), - ), - ), - }; - let definition_records = definitions - .as_ref() - .map(|definitions| definitions.definitions()) - .unwrap_or(&[]); - let mut keys = definition_records - .iter() - .map(|record| record.stable_key().clone()) - .collect::>(); - keys.sort(); - keys.dedup(); - let mut partial_work = SemanticDependencyManifestWork { - import_records_visited: imports.graph().records().len(), - ..SemanticDependencyManifestWork::default() - }; - let mut definition_fingerprints = Vec::with_capacity(definition_records.len()); - for record in definition_records { - partial_work.definition_records_visited += 1; - guard.accrue(QueryStructuralWork::DependencyManifest(Box::new( - partial_work, - ))); - definition_fingerprints.push(stable_definition_input_fingerprint(&snapshot, record)?); - } - let ( - mut declaration_type_dependencies, - mut declaration_type_call_head_dependencies, - mut builtin_type_call_head_inputs, - mut named_const_dependencies, - mut implicit_named_destructor_dependencies, - declaration_type_events_translated, - declaration_type_call_head_events_translated, - builtin_type_call_head_inputs_translated, - named_const_events_translated, - implicit_named_destructor_events_translated, - declaration_type_dependencies_complete, - declaration_type_call_head_dependencies_complete, - supported_type_call_heads_complete, - named_value_const_dependencies_complete, - implicit_named_destructor_dependencies_complete, - ) = match (&semantic, &definitions) { - (Ok(semantic), Ok(definitions)) => { - if definitions.source_revision() != &input.sources { - return Err(invalid_dependency_manifest( - "semantic dependency translation used a foreign definition revision", - )); - } - if semantic.body_owner_issuer().source_revision() != &input.sources { - return Err(invalid_dependency_manifest( - "semantic dependency translation used a stale body-owner issuer revision", - )); - } - let mut type_edges = Vec::new(); - for event in semantic.declaration_type_dependencies() { - let provenance = stable_declaration_source_endpoint(definitions, event)?; - type_edges.push(StableDeclarationTypeDependency { - source: match event.source_token { - Some(token) => stable_token_endpoint(semantic, token, &provenance)?, - None => provenance, - }, - target: stable_named_type_endpoint(definitions, event)?, - kind: event.dependency_kind, - }); - } - let mut type_call_head_edges = Vec::new(); - for event in semantic.declaration_type_call_head_dependencies() { - let provenance = stable_declaration_type_source_endpoint( - definitions, - event.source_file, - &event.source_name, - event.source_owner_name.as_deref(), - event.source_kind, - )?; - type_call_head_edges.push(StableDeclarationTypeCallHeadDependency { - source: match event.source_token { - Some(token) => stable_token_endpoint(semantic, token, &provenance)?, - None => provenance, - }, - callable: stable_free_function_endpoint( - definitions, - event.callable_file, - &event.callable_name, - )?, - kind: event.dependency_kind, - }); - } - let mut builtin_head_inputs = Vec::new(); - for event in semantic.declaration_builtin_type_call_head_dependencies() { - let provenance = stable_declaration_type_source_endpoint( - definitions, - event.source_file, - &event.source_name, - event.source_owner_name.as_deref(), - event.source_kind, - )?; - builtin_head_inputs.push(StableBuiltinTypeCallHeadInput { - source: match event.source_token { - Some(token) => stable_token_endpoint(semantic, token, &provenance)?, - None => provenance, - }, - builtin: event.builtin, - kind: event.dependency_kind, - }); - } - let mut const_edges = Vec::new(); - for event in semantic.named_const_dependencies() { - let source = stable_top_level_endpoint( - definitions, - event.source_file, - &event.source_name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ValueConst, - )?; - let target = match &event.target { - rue_air::NamedConstDependencyTargetEvent::ValueConst { file, name } => { - StableNamedConstDependencyTarget::ValueConst(stable_top_level_endpoint( - definitions, - *file, - name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ValueConst, - )?) - } - rue_air::NamedConstDependencyTargetEvent::FreeFunction { file, name } => { - StableNamedConstDependencyTarget::FreeFunction( - stable_free_function_endpoint(definitions, *file, name)?, - ) - } - rue_air::NamedConstDependencyTargetEvent::NamedType { - file, - name, - kind, - } => { - let kind = match kind { - rue_air::DeclarationTypeDependencyTargetKind::Struct => { - StableDefinitionKind::Struct - } - rue_air::DeclarationTypeDependencyTargetKind::Enum => { - StableDefinitionKind::Enum - } - rue_air::DeclarationTypeDependencyTargetKind::ValueConst => { - StableDefinitionKind::ValueConst - } - }; - let namespace = if matches!(kind, StableDefinitionKind::ValueConst) { - StableDefinitionNamespace::Value - } else { - StableDefinitionNamespace::Type - }; - StableNamedConstDependencyTarget::NamedType(stable_top_level_endpoint( - definitions, - *file, - name, - namespace, - kind, - )?) - } - rue_air::NamedConstDependencyTargetEvent::ModuleBinding { file, name } => { - StableNamedConstDependencyTarget::ModuleBinding( - stable_top_level_endpoint( - definitions, - *file, - name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ModuleBinding, - )?, - ) - } - }; - const_edges.push(StableNamedConstDependency { source, target }); - } - let mut implicit_destructor_edges = Vec::new(); - for event in semantic.implicit_named_destructor_dependencies() { - if matches!( - event.source, - rue_air::ImplicitDropDependencySourceEvent::Specialization { .. } - ) { - continue; - } - implicit_destructor_edges.push(StableImplicitNamedDestructorDependency { - source: stable_implicit_drop_source_endpoint( - semantic, - definitions, - &event.source, - )?, - target: stable_named_destructor_endpoint( - definitions, - event.target_file, - &event.target_owner_name, - )?, - }); - } - ( - type_edges, - type_call_head_edges, - builtin_head_inputs, - const_edges, - implicit_destructor_edges, - semantic.declaration_type_dependencies().len(), - semantic.declaration_type_call_head_dependencies().len(), - semantic - .declaration_builtin_type_call_head_dependencies() - .len(), - semantic.named_const_dependencies().len(), - semantic.implicit_named_destructor_dependencies().len(), - semantic.declaration_type_dependencies_complete(), - semantic.declaration_type_call_head_dependencies_complete(), - semantic.supported_type_call_heads_complete(), - semantic.named_value_const_dependencies_complete(), - semantic.implicit_named_destructor_dependencies_complete(), - ) - } - _ => ( - Vec::new(), - Vec::new(), - Vec::new(), - Vec::new(), - Vec::new(), - 0, - 0, - 0, - 0, - 0, - false, - false, - false, - false, - false, - ), - }; - // RUE-1027: body call edges have one production authority — the - // query-owned references each reached body transaction returns. - let mut free_function_dependencies = Vec::new(); - let mut free_function_caller_dependencies_complete = false; - if let Ok(semantic) = &semantic { - let mut query_edges = Vec::new(); - for function in semantic.functions() { - let Some(caller) = crate::revisioned_query_database::function_definition_key( - &function.semantic_identity, - ) else { - continue; - }; - let references = semantic - .body_references(&function.semantic_identity) - .ok_or_else(|| { - invalid_dependency_manifest( - "canonical function is missing its query-owned body references", - ) - })?; - for reference in references.0.iter() { - let crate::body_query::BodyReference::Callable(callable) = reference else { - continue; - }; - let Some(callee) = - crate::revisioned_query_database::function_definition_key(callable) - else { - continue; - }; - query_edges.push(StableFreeFunctionDependency { - caller: caller.clone(), - callee: callee.clone(), - }); - } - } - free_function_dependencies = query_edges; - free_function_caller_dependencies_complete = true; - } - let (mut analyzed_body_owners, anonymous_body_owners) = match (&semantic, &definitions) { - (Ok(semantic), Ok(definitions)) => { - let mut owners = Vec::new(); - let mut anonymous = 0usize; - for event in semantic.analyzed_body_owners() { - let owner = match stable_body_owner_endpoint(semantic, definitions, event)? { - Some(owner) => Some(owner), - None => { - anonymous += 1; - None - } - }; - if let Some(owner) = owner { - owners.push(owner); - } - } - (owners, anonymous) - } - _ => (Vec::new(), 0), - }; - analyzed_body_owners.sort(); - analyzed_body_owners.dedup(); - let mut body_named_dependencies = Vec::new(); - if let (Ok(semantic), Ok(definitions)) = (&semantic, &definitions) { - for event in semantic.body_named_dependencies() { - let Some((source, _)) = - stable_body_owner_endpoint(semantic, definitions, &event.source)? - else { - continue; - }; - let target = match &event.target { - rue_air::NamedConstDependencyTargetEvent::ValueConst { file, name } => { - stable_top_level_endpoint( - definitions, - *file, - name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ValueConst, - )? - } - rue_air::NamedConstDependencyTargetEvent::ModuleBinding { file, name } => { - stable_top_level_endpoint( - definitions, - *file, - name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ModuleBinding, - )? - } - // Body observers currently emit only value/module choices. - // Keep all other variants fail-closed if that contract changes. - _ => { - return Err(invalid_dependency_manifest( - "unsupported body-local named dependency target", - )); - } - }; - body_named_dependencies.push((source, target)); - } - } - body_named_dependencies.sort(); - body_named_dependencies.dedup(); - free_function_dependencies.sort(); - free_function_dependencies.dedup(); - declaration_type_dependencies.sort(); - declaration_type_dependencies.dedup(); - declaration_type_call_head_dependencies.sort(); - declaration_type_call_head_dependencies.dedup(); - builtin_type_call_head_inputs.sort(); - builtin_type_call_head_inputs.dedup(); - named_const_dependencies.sort(); - named_const_dependencies.dedup(); - implicit_named_destructor_dependencies.sort(); - implicit_named_destructor_dependencies.dedup(); - // A per-body record cannot authorize reuse when an observer-backed - // dependency surface for this semantic execution is incomplete. The - // current completeness evidence is whole-graph rather than per-owner, - // so conservatively retain its ownerless blockers on every record. - let mut whole_graph_body_blockers = BTreeSet::new(); - let mut block_body_surface = - |complete: bool, - surface: SemanticDependencySurface, - reason: SemanticDependencyIncompleteReason| { - if !complete { - whole_graph_body_blockers.insert(SemanticDependencyBlocker { - owner: None, - surface, - reason, - }); - } - }; - block_body_surface( - free_function_caller_dependencies_complete, - SemanticDependencySurface::FreeFunctionCall, - SemanticDependencyIncompleteReason::CallerEndpointUnavailable, - ); - block_body_surface( - implicit_named_destructor_dependencies_complete, - SemanticDependencySurface::ImplicitNamedDestructor, - SemanticDependencyIncompleteReason::AnonymousDropOwnerUnavailable, - ); - block_body_surface( - declaration_type_dependencies_complete, - SemanticDependencySurface::DeclarationType, - SemanticDependencyIncompleteReason::ResolvedTypeIdentityUnavailable, - ); - block_body_surface( - declaration_type_call_head_dependencies_complete, - SemanticDependencySurface::DeclarationTypeCallHead, - SemanticDependencyIncompleteReason::TypeCallHeadIdentityUnavailable, - ); - block_body_surface( - supported_type_call_heads_complete, - SemanticDependencySurface::SupportedTypeCallHead, - SemanticDependencyIncompleteReason::UnsupportedDynamicTypeCallHead, - ); - block_body_surface( - named_value_const_dependencies_complete, - SemanticDependencySurface::NamedValueConst, - SemanticDependencyIncompleteReason::ConstEndpointUnavailable, - ); - let mut body_dependencies = Vec::new(); - for (owner, generic) in &analyzed_body_owners { - let fingerprint = definition_fingerprints - .iter() - .find(|fingerprint| &fingerprint.key == owner) - .cloned() - .ok_or_else(|| { - invalid_dependency_manifest( - "analyzed body owner is absent from definition fingerprints", - ) - })?; - let mut direct_dependencies = Vec::new(); - if let Some(payload) = semantic.as_ref().ok().and_then(|semantic| { - semantic - .durable_ordinary_body_payloads() - .iter() - .find(|payload| &payload.owner == owner) - }) { - // Imported bodies intentionally skip source analysis and its - // declaration-type observers. The durable AIR is already - // joined to the same authoritative definition universe, so - // retain every named type/callable it embeds as direct input. - direct_dependencies.extend(payload.referenced_definition_keys()); - } - direct_dependencies.extend( - free_function_dependencies - .iter() - .filter(|edge| &edge.caller == owner) - .map(|edge| edge.callee.clone()), - ); - direct_dependencies.extend( - implicit_named_destructor_dependencies - .iter() - .filter(|edge| &edge.source == owner) - .map(|edge| edge.target.clone()), - ); - direct_dependencies.extend( - declaration_type_dependencies - .iter() - .filter(|edge| &edge.source == owner) - .map(|edge| edge.target.clone()), - ); - direct_dependencies.extend( - declaration_type_call_head_dependencies - .iter() - .filter(|edge| &edge.source == owner) - .map(|edge| edge.callable.clone()), - ); - direct_dependencies.extend( - body_named_dependencies - .iter() - .filter(|(source, _)| source == owner) - .map(|(_, target)| target.clone()), - ); - direct_dependencies.sort(); - direct_dependencies.dedup(); - let direct_dependency_inputs = direct_dependencies - .into_iter() - .map(|dependency| { - definition_fingerprints - .iter() - .find(|fingerprint| fingerprint.key == dependency) - .cloned() - .ok_or_else(|| { - invalid_dependency_manifest( - "body dependency is absent from definition fingerprints", - ) - }) - }) - .collect::, _>>()?; - let builtin_inputs = builtin_type_call_head_inputs - .iter() - .filter(|input| &input.source == owner) - .cloned() - .collect::>(); - let mut blockers = whole_graph_body_blockers - .iter() - .cloned() - .collect::>(); - if *generic { - blockers.push(SemanticDependencyBlocker { - owner: Some(owner.clone()), - surface: SemanticDependencySurface::GenericNamedMethodCall, - reason: - SemanticDependencyIncompleteReason::GenericSubstitutionIdentityUnavailable, - }); - } - blockers.sort(); - blockers.dedup(); - body_dependencies.push(StableBodyDependencyInputRecord { - owner: owner.clone(), - fingerprint, - target: input.target, - preview_features: input.preview_features.clone(), - direct_dependency_inputs: direct_dependency_inputs.into(), - builtin_type_call_heads: builtin_inputs.into(), - blockers: blockers.into(), - }); - } - body_dependencies.sort_by(|left, right| left.owner.cmp(&right.owner)); - let mut body_dependency_blockers = body_dependencies - .iter() - .flat_map(|record| record.blockers.iter().cloned()) - .collect::>(); - if anonymous_body_owners != 0 { - body_dependency_blockers.push(SemanticDependencyBlocker { - owner: None, - surface: SemanticDependencySurface::BodyOwner, - reason: SemanticDependencyIncompleteReason::AnonymousBodyOwnerUnavailable, - }); - } - body_dependency_blockers.sort(); - body_dependency_blockers.dedup(); - let mut durable_body_work = crate::DurableBodyWork::default(); - // Same single selection order as the CFG baseline above (RUE-1143). - let durable_declarations = self - .durable_baseline_override - .as_ref() - .and_then(|override_baseline| override_baseline.durable_declaration_cache.as_ref()) - .or_else(|| { - self.queries - .semantic - .last_good_record() - .and_then(|entry| entry.durable_declaration_cache.as_ref()) - }) - .map(|cache| cache.semantics.clone()); - let durable_ordinary_bodies = match &semantic { - Ok(semantic) => match crate::finalize_durable_ordinary_bodies( - semantic.durable_ordinary_body_payloads(), - &body_dependencies, - &mut durable_body_work, - ) { - Ok(candidates) if candidates.is_empty() => candidates, - Ok(candidates) => match durable_declarations { - None => { - durable_body_work.atomic_discards += 1; - Arc::from([]) - } - Some(declarations) => { - match crate::import_durable_declaration_semantics(&declarations) { - Ok(epoch) => { - let mut installed_instructions = 0; - let mut installed_places = 0; - let mut installed_strings = 0; - let mut failed = false; - for candidate in candidates.iter() { - let dto = match candidate - .project_semantic_body(&mut durable_body_work) - { - Ok(dto) => dto, - Err(reason) => { - durable_body_work.last_projection_failure = - Some(reason); - durable_body_work.atomic_discards += 1; - failed = true; - break; - } - }; - let owner_records = definition_records - .iter() - .filter(|record| record.stable_key() == candidate.owner()) - .collect::>(); - let [owner_record] = owner_records.as_slice() else { - durable_body_work.record_import_failure( - rue_air::SemanticBodyImportFailureKind::StructuralValidation, - 1, - ); - durable_body_work.atomic_discards += 1; - failed = true; - break; - }; - let Some(body_span) = owner_record.body_span() else { - durable_body_work.record_import_failure( - rue_air::SemanticBodyImportFailureKind::StructuralValidation, - 1, - ); - durable_body_work.atomic_discards += 1; - failed = true; - break; - }; - durable_body_work.import_attempts += 1; - match epoch.import_body(&dto, body_span) { - Ok(imported) => { - durable_body_work.import_successes += 1; - installed_instructions += imported.air.len(); - installed_places += imported.air.places().len(); - installed_strings += imported.strings.len(); - } - Err(reason) => { - durable_body_work - .record_import_failure(reason.kind(), 1); - durable_body_work.atomic_discards += 1; - failed = true; - break; - } - } - } - if failed { - durable_body_work.installed_instructions += - installed_instructions; - durable_body_work.installed_places += installed_places; - durable_body_work.installed_strings += installed_strings; - Arc::from([]) - } else { - durable_body_work.installed_instructions += - installed_instructions; - durable_body_work.installed_places += installed_places; - durable_body_work.installed_strings += installed_strings; - candidates - } - } - Err(reason) => { - durable_body_work.record_import_failure( - rue_air::SemanticBodyImportFailureKind::Semantic(reason), - 1, - ); - durable_body_work.atomic_discards += 1; - Arc::from([]) - } - } - } - }, - Err(_) => Arc::from([]), - }, - Err(_) => Arc::from([]), - }; - let work = SemanticDependencyManifestWork { - definition_records_visited: partial_work.definition_records_visited, - import_records_visited: partial_work.import_records_visited, - declaration_type_events_translated, - declaration_type_call_head_events_translated, - builtin_type_call_head_inputs_translated, - named_const_events_translated, - implicit_named_destructor_events_translated, - body_owner_events_translated: analyzed_body_owners.len() + anonymous_body_owners, - body_named_events_translated: body_named_dependencies.len(), - body_dependency_records_built: body_dependencies.len(), - durable_bodies: durable_body_work, - extra_rir_instructions_visited: 0, - }; - *attempt_work = Some(work); - guard.accrue(QueryStructuralWork::DependencyManifest(Box::new(work))); - let module_imports = imports - .graph() - .records() - .iter() - .map(|record| match record.resolution() { - CanonicalImportResolution::Resolved(target) => { - StableModuleImportDependency::Resolved { - importer: record.importer().clone(), - normalized_specifier: Arc::from(record.normalized_specifier()), - target: target.clone(), - } - } - CanonicalImportResolution::Missing => StableModuleImportDependency::Missing { - importer: record.importer().clone(), - normalized_specifier: Arc::from(record.normalized_specifier()), - }, - CanonicalImportResolution::Ambiguous { - file_module, - directory_module, - } => StableModuleImportDependency::Ambiguous { - importer: record.importer().clone(), - normalized_specifier: Arc::from(record.normalized_specifier()), - file_module: file_module.clone(), - directory_module: directory_module.clone(), - }, - }) - .collect::>(); - let mut dependency_blockers = whole_graph_body_blockers; - let mut block = |complete: bool, - surface: SemanticDependencySurface, - reason: SemanticDependencyIncompleteReason| { - if !complete { - dependency_blockers.insert(SemanticDependencyBlocker { - owner: None, - surface, - reason, - }); - } - }; - block( - free_function_caller_dependencies_complete, - SemanticDependencySurface::FreeFunctionCall, - SemanticDependencyIncompleteReason::CallerEndpointUnavailable, - ); - block( - implicit_named_destructor_dependencies_complete, - SemanticDependencySurface::ImplicitNamedDestructor, - SemanticDependencyIncompleteReason::AnonymousDropOwnerUnavailable, - ); - block( - declaration_type_dependencies_complete, - SemanticDependencySurface::DeclarationType, - SemanticDependencyIncompleteReason::ResolvedTypeIdentityUnavailable, - ); - block( - declaration_type_call_head_dependencies_complete, - SemanticDependencySurface::DeclarationTypeCallHead, - SemanticDependencyIncompleteReason::TypeCallHeadIdentityUnavailable, - ); - block( - supported_type_call_heads_complete, - SemanticDependencySurface::SupportedTypeCallHead, - SemanticDependencyIncompleteReason::UnsupportedDynamicTypeCallHead, - ); - block( - named_value_const_dependencies_complete, - SemanticDependencySurface::NamedValueConst, - SemanticDependencyIncompleteReason::ConstEndpointUnavailable, - ); - let manifest = Arc::new(SemanticDependencyInputManifest { - input: input.clone(), - imports: imports.graph().clone(), - definitions: keys.into(), - definition_fingerprints: definition_fingerprints.into(), - module_imports: module_imports.into(), - free_function_dependencies: free_function_dependencies.into(), - implicit_named_destructor_dependencies: implicit_named_destructor_dependencies.into(), - declaration_type_dependencies: declaration_type_dependencies.into(), - declaration_type_call_head_dependencies: declaration_type_call_head_dependencies.into(), - builtin_type_call_head_inputs: builtin_type_call_head_inputs.into(), - named_const_dependencies: named_const_dependencies.into(), - body_dependencies: body_dependencies.into(), - durable_ordinary_bodies, - durable_body_candidate_state: DurableBodyCandidateState::from_blockers( - body_dependency_blockers, - ), - dependency_graph_state: SemanticDependencyGraphState::from_blockers( - dependency_blockers.into_iter().collect(), - ), - definition_universe_state, - work, - }); - let semantic_key = SemanticQueryKey { - input: CodegenInputDescriptor { - semantic: input.clone(), - opt_level: options.opt_level.into(), - }, - imports: imports.graph().clone(), - }; - let definition_key = DefinitionQueryKey { - input: input.clone(), - imports: imports.graph().clone(), - }; - let mut dependencies = vec![ - self.queries - .source_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .import_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .target_inputs - .selected(&self.queries.graph) - .unwrap(), - self.queries - .preview_inputs - .selected(&self.queries.graph) - .unwrap(), - ]; - if let Some((_, semantic_handle)) = self.queries.semantic.get_with_handle(&semantic_key) { - dependencies.push(semantic_handle.observed()); - } else if let Some((_, merge_handle)) = - self.queries.merge.get_secondary_with_handle(&input.sources) - { - // Semantic requests rejected by merge/RIR do not publish a - // semantic terminal. Retain the actual failing upstream terminal - // read by this incomplete manifest instead. - dependencies.push(merge_handle.observed()); - } - if let Some(definition_handle) = self.queries.definitions.handle(&definition_key) { - dependencies.push(definition_handle.observed()); - } - let handle = self - .queries - .manifests - .publish_selected_attempt( - &mut self.queries.graph, - DependencyManifestCacheEntry { - key, - result: Ok(manifest.clone()), - }, - dependencies, - guard.structural(), - *execution, - ) - .unwrap_or_else(query_control_error); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - Ok(manifest) - } - - /// Compare two immutable semantic manifests without lowering or scanning RIR. - /// - /// Supported production manifests with complete dependency capture can produce - /// an incremental invalidation plan. Unsupported dependency surfaces, incomplete - /// capture, and global semantic-input changes fail closed to full invalidation. - pub(crate) fn semantic_invalidation_plan( - &mut self, - previous: &Arc, - current: &Arc, - ) -> Arc { - let mut guard = self.metrics.begin::(); - let attempt_id = guard.id; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let key = InvalidationPlanQueryKey { - previous: previous.clone(), - current: current.clone(), - }; - if let Some((entry, handle)) = self - .queries - .invalidation_plans - .request_selected(&mut self.queries.graph, key.clone(), attempt_id.0) - .expect("invalidation planning cannot recursively request itself") - { - guard.bind(handle.as_view()); - return ( - entry.plan.clone(), - QueryAttemptExecution::Reused, - Some(handle.origin_attempt_id()), - QueryStructuralWork::None, - ); - } - guard.started(); - let plan = Arc::new(plan_semantic_invalidation(previous, current)); - let structural = QueryStructuralWork::Invalidation(plan.work()); - guard.accrue(structural.clone()); - let mut dependencies = Vec::new(); - for manifest in [previous, current] { - let manifest_key = DependencyManifestQueryKey { - input: manifest.input.clone(), - imports: manifest.imports.clone(), - }; - if let Some(handle) = self.queries.manifests.handle(&manifest_key) { - dependencies.push(handle.observed()); - } - } - let handle = self - .queries - .invalidation_plans - .publish_selected_attempt( - &mut self.queries.graph, - InvalidationPlanCacheEntry { - key, - plan: plan.clone(), - }, - dependencies, - guard.structural(), - QueryAttemptExecution::Computed, - ) - .unwrap_or_else(query_control_error); - guard.bind(handle.as_view()); - self.refresh_retention_metrics(); - (plan, QueryAttemptExecution::Computed, None, structural) - })); - let (plan, execution, origin, structural) = match result { - Ok(result) => result, - Err(payload) => self.resume_canceled_query(&mut guard, payload), - }; - guard.finish(execution, origin, &Ok::<(), ()>(()), structural); - self.metrics.synchronize(); - plan - } - - pub fn unstable_invalidation_metrics( - &mut self, - previous: &crate::unstable::DependencyBaseline, - current: &crate::unstable::DependencyBaseline, - ) -> Result { - if !previous.belongs_to(&self.identity) || !current.belongs_to(&self.identity) { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "dependency baselines belong to a different compiler session".into(), - ), - ))); - } - let plan = self.semantic_invalidation_plan(&previous.inner, ¤t.inner); - Ok(crate::unstable::InvalidationMetrics::from_plan(&plan)) - } -} - -fn plan_semantic_invalidation( - previous: &SemanticDependencyInputManifest, - current: &SemanticDependencyInputManifest, -) -> SemanticInvalidationPlan { - let previous_fingerprints = previous - .definition_fingerprints - .iter() - .map(|entry| (entry.key.clone(), entry)) - .collect::>(); - let current_fingerprints = current - .definition_fingerprints - .iter() - .map(|entry| (entry.key.clone(), entry)) - .collect::>(); - let mut work = SemanticInvalidationWork::default(); - let mut added = BTreeSet::new(); - let mut removed = BTreeSet::new(); - let mut changed = BTreeSet::new(); - for (key, fingerprint) in ¤t_fingerprints { - match previous_fingerprints.get(key) { - None => { - added.insert(key.clone()); - } - Some(previous) => { - work.definition_fingerprints_compared += 1; - if *previous != *fingerprint { - changed.insert(key.clone()); - } - } - } - } - for key in previous_fingerprints.keys() { - if !current_fingerprints.contains_key(key) { - removed.insert(key.clone()); - } - } - - let mut reasons = BTreeSet::new(); - if previous.input.sources.root() != current.input.sources.root() { - reasons.insert(SemanticFullInvalidationReason::RootChanged); - } - if previous.module_imports != current.module_imports { - reasons.insert(SemanticFullInvalidationReason::ModuleImportsChanged); - } - if previous.input.target != current.input.target { - reasons.insert(SemanticFullInvalidationReason::TargetChanged); - } - if previous.input.preview_features != current.input.preview_features { - reasons.insert(SemanticFullInvalidationReason::PreviewFeaturesChanged); - } - for state in [ - &previous.definition_universe_state, - ¤t.definition_universe_state, - ] { - match state { - SemanticDefinitionUniverseState::Complete => {} - SemanticDefinitionUniverseState::Incomplete(reason) => match reason { - SemanticDefinitionUniverseIncompleteReason::StableDefinitionsFailed(failures) => { - assert!(!failures.failures.is_empty()); - reasons.insert(SemanticFullInvalidationReason::IncompleteDefinitionUniverse); - } - }, - } - } - let mut dependency_blockers = BTreeSet::new(); - for graph in [ - &previous.dependency_graph_state, - ¤t.dependency_graph_state, - ] { - graph.fold_planning_blockers(&mut dependency_blockers); - } - if !dependency_blockers.is_empty() { - reasons.insert(SemanticFullInvalidationReason::IncompleteDependencyGraph( - dependency_blockers.into_iter().collect::>().into(), - )); - } - - let mut invalidated = BTreeSet::new(); - let mut reusable = BTreeSet::new(); - let scope = if reasons.is_empty() { - invalidated.extend(added.iter().cloned()); - invalidated.extend(removed.iter().cloned()); - invalidated.extend(changed.iter().cloned()); - let mut reverse = BTreeMap::>::new(); - collect_reverse_dependencies(previous, &mut reverse, &mut work); - collect_reverse_dependencies(current, &mut reverse, &mut work); - let mut queue = invalidated.iter().cloned().collect::>(); - while let Some(key) = queue.pop_front() { - work.reverse_closure_nodes_visited += 1; - if let Some(dependents) = reverse.get(&key) { - for dependent in dependents { - if invalidated.insert(dependent.clone()) { - queue.push_back(dependent.clone()); - } - } - } - } - reusable.extend( - current_fingerprints - .keys() - .filter(|key| !invalidated.contains(*key)) - .cloned(), - ); - SemanticInvalidationScope::Incremental - } else { - SemanticInvalidationScope::Full { - reasons: reasons.into_iter().collect::>().into(), - } - }; - SemanticInvalidationPlan { - scope, - added: added.into_iter().collect::>().into(), - removed: removed.into_iter().collect::>().into(), - changed: changed.into_iter().collect::>().into(), - invalidated: invalidated.into_iter().collect::>().into(), - reusable: reusable.into_iter().collect::>().into(), - work, - } -} - -fn collect_reverse_dependencies( - manifest: &SemanticDependencyInputManifest, - reverse: &mut BTreeMap>, - work: &mut SemanticInvalidationWork, -) { - let mut add = |source: &StableDefinitionKey, target: &StableDefinitionKey| { - work.dependency_edges_visited += 1; - reverse - .entry(target.clone()) - .or_default() - .insert(source.clone()); - }; - for edge in manifest.free_function_dependencies.iter() { - add(&edge.caller, &edge.callee); - } - for edge in manifest.implicit_named_destructor_dependencies.iter() { - add(&edge.source, &edge.target); - } - for edge in manifest.declaration_type_dependencies.iter() { - add(&edge.source, &edge.target); - } - for edge in manifest.declaration_type_call_head_dependencies.iter() { - add(&edge.source, &edge.callable); - } - for edge in manifest.named_const_dependencies.iter() { - let target = match &edge.target { - StableNamedConstDependencyTarget::ValueConst(key) - | StableNamedConstDependencyTarget::FreeFunction(key) - | StableNamedConstDependencyTarget::NamedType(key) - | StableNamedConstDependencyTarget::ModuleBinding(key) => key, - }; - add(&edge.source, target); - } -} - -const DEFINITION_FINGERPRINT_SCHEMA_V2: u16 = 2; -const DEFINITION_DECLARATION_DOMAIN_V2: &[u8] = b"rue.definition.declaration\0v2\0sha256\0"; -const DEFINITION_SIGNATURE_DOMAIN_V2: &[u8] = b"rue.definition.signature\0v2\0sha256\0"; -const DEFINITION_BODY_DOMAIN_V2: &[u8] = b"rue.definition.body-or-initializer\0v2\0sha256\0"; - -fn declaration_shell_failure_diagnostics( - program: &crate::canonical_merge::CanonicalMergedAst, - failure: &crate::declaration_candidate::DeclarationShellFailure, -) -> CompileErrors { - use crate::declaration_candidate::DeclarationShellFailure as F; - let key = match failure { - F::Absent(key) | F::Ambiguous(key) | F::ParserCapabilityMismatch(key) => Some(key), - F::OccurrencesUnavailable(_) => None, - }; - let span = key.and_then(|key| { - program - .modules() - .iter() - .find(|module| module.module_id() == &key.module) - .and_then(|module| module.definitions().declaration_locator(key)) - .map(|locator| locator.declaration_span) - }); - let kind = ErrorKind::InternalError(format!( - "query-owned declaration shell failed stable validation: {failure:?}" - )); - CompileErrors::from(match span { - Some(span) => CompileError::new(kind, span), - None => CompileError::without_span(kind), - }) -} - -fn semantic_nucleus_failure_diagnostics( - program: &crate::canonical_merge::CanonicalMergedAst, - declaration: Option<&crate::declaration_candidate::DeclarationCandidateKey>, - failure: &crate::semantic_query_nucleus::SemanticNucleusFailure, -) -> CompileErrors { - use crate::semantic_query_nucleus::SemanticNucleusFailure as F; - if let (Some(declaration), F::DiagnosticAtParameter { kind, ordinal }) = (declaration, failure) - && let Some(module) = program - .modules() - .iter() - .find(|module| module.module_id() == &declaration.module) - && let Some(locator) = module.definitions().declaration_locator(declaration) - { - let parameters = module.ast().items.iter().find_map(|item| match item { - rue_parser::ast::Item::Function(function) - if function.span == locator.declaration_span => - { - Some(function.params.as_slice()) - } - rue_parser::ast::Item::Struct(structure) => structure - .methods - .iter() - .find(|method| method.span == locator.declaration_span) - .map(|method| method.params.as_slice()), - rue_parser::ast::Item::Extern(block) => block - .fns - .iter() - .find(|function| function.span == locator.declaration_span) - .map(|function| function.params.as_slice()), - _ => None, - }); - if let Some(parameter) = parameters.and_then(|parameters| parameters.get(*ordinal as usize)) - { - return CompileErrors::from(CompileError::new(kind.clone(), parameter.span)); - } - } - if let F::DiagnosticAtDeclaration { kind, declaration } = failure - && let Some(span) = program - .modules() - .iter() - .find(|module| module.module_id() == &declaration.module) - .and_then(|module| module.definitions().declaration_locator(declaration)) - .map(|locator| locator.declaration_span) - { - return CompileErrors::from(CompileError::new(kind.clone(), span)); - } - if let (Some(declaration), F::DiagnosticAtProducerRange { kind, start, end }) = - (declaration, failure) - && let Some(producer) = program - .modules() - .iter() - .find(|module| module.module_id() == &declaration.module) - .and_then(|module| module.definitions().producer_fragment_span(declaration)) - && let (Some(start), Some(end)) = ( - producer.start.checked_add(*start), - producer.start.checked_add(*end), - ) - && start <= end - && end <= producer.end - { - return CompileErrors::from(CompileError::new( - kind.clone(), - rue_span::Span::with_file(producer.file_id, start, end), - )); - } - if let F::OwnershipGate { kind, gate } = failure { - let primary_span = declaration.and_then(|key| { - program - .modules() - .iter() - .find(|module| module.module_id() == &key.module) - .and_then(|module| module.definitions().declaration_locator(key)) - .map(|locator| locator.declaration_span) - }); - let mut error = match primary_span { - Some(span) => CompileError::new(kind.clone(), span), - None => CompileError::without_span(kind.clone()), - }; - if let Some(application) = &gate.application - && let Some(span) = program - .modules() - .iter() - .find(|module| module.module_id() == &application.declaration.module) - .and_then(|module| { - module - .definitions() - .declaration_locator(&application.declaration) - }) - .map(|locator| locator.declaration_span) - { - error = error.with_label("required by the type-constructor application here", span); - } - return CompileErrors::from(error); - } - if let (Some(declaration), F::Diagnostic(ErrorKind::CopyStructWithDestructor { type_name })) = - (declaration, failure) - && let Some(module) = program - .modules() - .iter() - .find(|module| module.module_id() == &declaration.module) - { - let destructor_span = module.ast().items.iter().find_map(|item| match item { - rue_parser::ast::Item::DropFn(drop) - if module.resolve_raw_symbol(drop.type_name.name) == type_name => - { - Some(drop.span) - } - _ => None, - }); - let copy_span = module.ast().items.iter().find_map(|item| match item { - rue_parser::ast::Item::Struct(structure) - if module.resolve_raw_symbol(structure.name.name) == type_name => - { - structure - .directives - .iter() - .find(|directive| module.resolve_raw_symbol(directive.name.name) == "copy") - .map(|directive| directive.span) - } - _ => None, - }); - if let Some(destructor_span) = destructor_span { - let mut error = CompileError::new( - ErrorKind::CopyStructWithDestructor { - type_name: type_name.clone(), - }, - destructor_span, - ) - .with_label("destructor defined here", destructor_span) - .with_note( - "`@copy` values are duplicated implicitly, so the destructor would run \ - once per copy — cleaning up the same resource multiple times", - ) - .with_help("remove the `@copy` attribute or remove the `drop fn`"); - if let Some(copy_span) = copy_span { - error = error.with_label("type declared `@copy` here", copy_span); - } - return CompileErrors::from(error); - } - } - let span = declaration.and_then(|key| { - program - .modules() - .iter() - .find(|module| module.module_id() == &key.module) - .and_then(|module| module.definitions().declaration_locator(key)) - .map(|locator| locator.declaration_span) - }); - let (kind, help) = match failure { - F::Diagnostic(kind) => (kind.clone(), None), - F::DiagnosticAtParameter { kind, .. } => (kind.clone(), None), - F::DiagnosticAtDeclaration { kind, .. } => (kind.clone(), None), - F::DiagnosticAtProducerRange { kind, .. } => (kind.clone(), None), - F::OwnershipGate { kind, .. } => (kind.clone(), None), - F::DiagnosticWithHelp { kind, help } => (kind.clone(), Some(help.clone())), - F::Cycle(nodes) => ( - ErrorKind::ConstInitializerCycle { - cycle: nodes - .iter() - .map(AsRef::as_ref) - .collect::>() - .join(" -> "), - }, - None, - ), - F::SignatureReentry { cycle, .. } => ( - ErrorKind::UnknownType( - cycle - .iter() - .map(AsRef::as_ref) - .collect::>() - .join(" -> "), - ), - None, - ), - F::Resolution(message) if message.starts_with("unknown array length") => ( - ErrorKind::InvalidArrayLength { - reason: message - .strip_prefix("unknown array length `") - .and_then(|name| name.strip_suffix('`')) - .map_or_else( - || message.to_string(), - |name| format!("'{name}' is not a compile-time constant"), - ), - }, - None, - ), - F::Resolution(message) => ( - ErrorKind::ComptimeEvaluationFailed { - reason: message.to_string(), - }, - None, - ), - F::Shell(message) | F::Syntax(message) => ( - ErrorKind::InternalError(format!("semantic query invariant failed: {message}")), - None, - ), - }; - let error = match span { - Some(span) => CompileError::new(kind, span), - None => CompileError::without_span(kind), - }; - CompileErrors::from(match help { - Some(help) => error.with_help(help.to_string()), - None => error, - }) -} - -fn well_known_option_resolution_diagnostics( - program: &crate::canonical_merge::CanonicalMergedAst, - failure: &crate::revisioned_query_database::WellKnownOptionResolutionFailure, -) -> CompileErrors { - use crate::revisioned_query_database::WellKnownOptionResolutionFailure as F; - match failure { - F::Incomplete { - payload, - prerequisite, - detail, - } => CompileErrors::from(CompileError::without_span(ErrorKind::InternalError( - format!( - "exact trusted Option({payload:?}) prerequisite resolution was incomplete{}: {detail}", - prerequisite - .as_ref() - .map_or_else(String::new, |key| format!(" at {key:?}")) - ), - ))), - F::Semantic { payload, failure } => { - let mut errors = semantic_nucleus_failure_diagnostics(program, None, failure); - if errors.is_empty() { - errors = CompileErrors::from(CompileError::without_span(ErrorKind::InternalError( - format!( - "trusted Option({payload:?}) resolution failed without diagnostics: {failure:?}" - ), - ))); - } - errors - } - F::WrongProjection { payload, detail } => CompileErrors::from(CompileError::without_span( - ErrorKind::InternalError(format!( - "trusted Option({payload:?}) resolution returned the wrong semantic projection: {detail}" - )), - )), - } -} - -pub(crate) fn stable_definition_input_fingerprint( - snapshot: &SourceSnapshot, - record: &crate::BoundDefinitionRecord, -) -> Result { - stable_definition_input_fingerprint_parts( - snapshot, - record.stable_key(), - record.visibility(), - record.input_partition(), - ) -} - -fn stable_definition_input_fingerprint_parts( - snapshot: &SourceSnapshot, - key: &StableDefinitionKey, - visibility: Option, - partition: crate::bound_definitions::BoundDefinitionInputPartition, -) -> Result { - let source_fragment = |span: Span| -> Result<&str, CompileErrors> { - let source = snapshot.source_text(span.file_id).ok_or_else(|| { - invalid_dependency_manifest("definition fingerprint span references an absent source") - })?; - let start = usize::try_from(span.start).map_err(|_| { - invalid_dependency_manifest( - "definition fingerprint span start cannot address this host", - ) - })?; - let end = usize::try_from(span.end).map_err(|_| { - invalid_dependency_manifest("definition fingerprint span end cannot address this host") - })?; - source.get(start..end).ok_or_else(|| { - invalid_dependency_manifest( - "definition fingerprint span is reversed, out of bounds, or not on UTF-8 boundaries", - ) - }) - }; - - let mut declaration = FramedDefinitionHasher::new(DEFINITION_DECLARATION_DOMAIN_V2); - hash_stable_definition_key(&mut declaration, key); - declaration.frame(&[match visibility { - None => 0, - Some(rue_parser::ast::Visibility::Private) => 1, - Some(rue_parser::ast::Visibility::Public) => 2, - }]); - let (signature_spans, payload_span, precision) = match partition { - crate::bound_definitions::BoundDefinitionInputPartition::Body { signature, body } => ( - vec![signature], - Some(body), - StableDefinitionFingerprintPrecision::SignatureAndBody, - ), - crate::bound_definitions::BoundDefinitionInputPartition::Initializer { - signature, - initializer, - } => ( - vec![signature], - Some(initializer), - StableDefinitionFingerprintPrecision::SignatureAndInitializer, - ), - crate::bound_definitions::BoundDefinitionInputPartition::ExactSignature(spans) => ( - spans.to_vec(), - None, - StableDefinitionFingerprintPrecision::ExactSignature, - ), - }; - let mut signature = FramedDefinitionHasher::new(DEFINITION_SIGNATURE_DOMAIN_V2); - for span in signature_spans { - signature.frame(source_fragment(span)?.as_bytes()); - } - let body_or_initializer = payload_span - .map(|span| { - let mut payload = FramedDefinitionHasher::new(DEFINITION_BODY_DOMAIN_V2); - payload.frame(source_fragment(span)?.as_bytes()); - Ok::<_, CompileErrors>(payload.finish()) - }) - .transpose()?; - Ok(StableDefinitionInputFingerprint { - schema_version: DEFINITION_FINGERPRINT_SCHEMA_V2, - key: key.clone(), - declaration: declaration.finish(), - signature: signature.finish(), - body_or_initializer, - precision, - }) -} - -struct FramedDefinitionHasher(Sha256); - -impl FramedDefinitionHasher { - fn new(domain: &[u8]) -> Self { - let mut hasher = Sha256::new(); - hasher.update(domain); - Self(hasher) - } - - fn frame(&mut self, bytes: &[u8]) { - self.0.update((bytes.len() as u64).to_le_bytes()); - self.0.update(bytes); - } - - fn finish(self) -> StableDefinitionFingerprint { - StableDefinitionFingerprint(self.0.finalize().into()) - } -} - -fn hash_stable_definition_key(hasher: &mut FramedDefinitionHasher, key: &StableDefinitionKey) { - hasher.frame(key.module().as_str().as_bytes()); - hasher.frame(&[stable_namespace_tag(key.namespace())]); - hasher.frame(&[stable_kind_tag(key.kind())]); - hasher.frame(key.name().as_bytes()); - match key.owner() { - None => hasher.frame(&[]), - Some(owner) => { - hasher.frame(&[1]); - hasher.frame(owner.module().as_str().as_bytes()); - hasher.frame(&[stable_kind_tag(owner.kind())]); - hasher.frame(owner.name().as_bytes()); - } - } -} - -fn stable_namespace_tag(namespace: StableDefinitionNamespace) -> u8 { - match namespace { - StableDefinitionNamespace::Value => 0, - StableDefinitionNamespace::Type => 1, - StableDefinitionNamespace::Destructor => 2, - StableDefinitionNamespace::Method => 3, - } -} - -fn stable_kind_tag(kind: StableDefinitionKind) -> u8 { - match kind { - StableDefinitionKind::Function => 0, - StableDefinitionKind::Struct => 1, - StableDefinitionKind::Enum => 2, - StableDefinitionKind::ValueConst => 3, - StableDefinitionKind::ModuleBinding => 4, - StableDefinitionKind::Destructor => 5, - StableDefinitionKind::Method => 6, - StableDefinitionKind::AssociatedFunction => 7, - } -} - -fn stable_free_function_endpoint( - definitions: &BoundDefinitionSet, - file: u32, - name: &str, -) -> Result { - let matches = definitions - .definitions() - .iter() - .filter(|record| { - record.declaration_span().file_id.index() == file - && record.stable_key().name() == name - && record.stable_key().namespace() == StableDefinitionNamespace::Value - && record.stable_key().kind() == StableDefinitionKind::Function - }) - .collect::>(); - let [record] = matches.as_slice() else { - return Err(invalid_dependency_manifest(&format!( - "free-function dependency endpoint ({file}, '{name}') did not join exactly one bound function", - ))); - }; - Ok(record.stable_key().clone()) -} - -fn stable_body_owner_endpoint( - semantic: &CanonicalSemanticOutput, - definitions: &BoundDefinitionSet, - event: &rue_air::AnalyzedBodyOwnerEvent, -) -> Result, CompileErrors> { - let (token, provenance, generic) = match event { - rue_air::AnalyzedBodyOwnerEvent::FreeFunction { token, file, name } => ( - *token, - stable_free_function_endpoint(definitions, *file, name)?, - false, - ), - rue_air::AnalyzedBodyOwnerEvent::NamedMethod { - token, - file, - owner_name, - method_name, - generic, - } => ( - *token, - stable_named_method_endpoint(definitions, *file, owner_name, method_name)?, - *generic, - ), - rue_air::AnalyzedBodyOwnerEvent::NamedDestructor { - token, - file, - owner_name, - } => ( - *token, - stable_named_destructor_endpoint(definitions, *file, owner_name)?, - false, - ), - rue_air::AnalyzedBodyOwnerEvent::Anonymous => return Ok(None), - }; - let authoritative = semantic - .body_owner_issuer() - .key_for_body_token(token) - .map_err(CompileErrors::from)?; - if authoritative != &provenance { - return Err(invalid_dependency_manifest( - "body owner token does not match its checked source provenance", - )); - } - Ok(Some((authoritative.clone(), generic))) -} - -fn stable_token_endpoint( - semantic: &CanonicalSemanticOutput, - token: rue_air::BodyOwnerToken, - provenance: &StableDefinitionKey, -) -> Result { - let authoritative = semantic - .body_owner_issuer() - .key_for_body_token(token) - .map_err(CompileErrors::from)?; - if authoritative != provenance { - return Err(invalid_dependency_manifest( - "body-local observation token does not match its checked source provenance", - )); - } - Ok(authoritative.clone()) -} - -fn stable_implicit_drop_source_endpoint( - semantic: &CanonicalSemanticOutput, - definitions: &BoundDefinitionSet, - source: &rue_air::ImplicitDropDependencySourceEvent, -) -> Result { - match source { - rue_air::ImplicitDropDependencySourceEvent::Anonymous => Err(invalid_dependency_manifest( - "anonymous drop-dependency source has no stable endpoint", - )), - rue_air::ImplicitDropDependencySourceEvent::Specialization { .. } => { - Err(invalid_dependency_manifest( - "specialized drop-dependency source requires specialization identity", - )) - } - rue_air::ImplicitDropDependencySourceEvent::FreeFunction { token, file, name } => { - let provenance = stable_free_function_endpoint(definitions, *file, name)?; - stable_token_endpoint(semantic, *token, &provenance) - } - rue_air::ImplicitDropDependencySourceEvent::NamedMethod { - token, - file, - owner_name, - method_name, - } => { - let provenance = - stable_named_method_endpoint(definitions, *file, owner_name, method_name)?; - stable_token_endpoint(semantic, *token, &provenance) - } - rue_air::ImplicitDropDependencySourceEvent::NamedDestructor { - token, - file, - owner_name, - } => { - let provenance = stable_named_destructor_endpoint(definitions, *file, owner_name)?; - stable_token_endpoint(semantic, *token, &provenance) - } - rue_air::ImplicitDropDependencySourceEvent::NamedStruct { file, name } => { - stable_top_level_endpoint( - definitions, - *file, - name, - StableDefinitionNamespace::Type, - StableDefinitionKind::Struct, - ) - } - rue_air::ImplicitDropDependencySourceEvent::NamedEnum { file, name } => { - stable_top_level_endpoint( - definitions, - *file, - name, - StableDefinitionNamespace::Type, - StableDefinitionKind::Enum, - ) - } - } -} - -fn stable_named_method_endpoint( - definitions: &BoundDefinitionSet, - file: u32, - owner_name: &str, - method_name: &str, -) -> Result { - let matches = definitions - .definitions() - .iter() - .filter(|record| { - let key = record.stable_key(); - record.declaration_span().file_id.index() == file - && key.name() == method_name - && key.namespace() == StableDefinitionNamespace::Method - && matches!( - key.kind(), - StableDefinitionKind::Method | StableDefinitionKind::AssociatedFunction - ) - && key.owner().is_some_and(|owner| owner.name() == owner_name) - }) - .collect::>(); - let [record] = matches.as_slice() else { - return Err(invalid_dependency_manifest(&format!( - "named-method dependency endpoint ({file}, '{owner_name}', '{method_name}') did not join exactly one bound method", - ))); - }; - Ok(record.stable_key().clone()) -} - -fn stable_named_destructor_endpoint( - definitions: &BoundDefinitionSet, - file: u32, - owner_name: &str, -) -> Result { - let matches = definitions - .definitions() - .iter() - .filter(|record| { - let key = record.stable_key(); - record.declaration_span().file_id.index() == file - && key.name() == owner_name - && key.namespace() == StableDefinitionNamespace::Destructor - && key.kind() == StableDefinitionKind::Destructor - && key.owner().is_some_and(|owner| owner.name() == owner_name) - }) - .collect::>(); - let [record] = matches.as_slice() else { - return Err(invalid_dependency_manifest(&format!( - "named-destructor dependency endpoint ({file}, '{owner_name}') did not join exactly one bound destructor", - ))); - }; - Ok(record.stable_key().clone()) -} - -fn stable_declaration_source_endpoint( - definitions: &BoundDefinitionSet, - event: &rue_air::DeclarationTypeDependencyEvent, -) -> Result { - stable_declaration_type_source_endpoint( - definitions, - event.source_file, - &event.source_name, - event.source_owner_name.as_deref(), - event.source_kind, - ) -} - -fn stable_declaration_type_source_endpoint( - definitions: &BoundDefinitionSet, - source: u32, - source_name: &str, - source_owner_name: Option<&str>, - source_kind: rue_air::DeclarationTypeDependencySourceKind, -) -> Result { - use rue_air::DeclarationTypeDependencySourceKind as K; - match source_kind { - K::Function => stable_free_function_endpoint(definitions, source, source_name), - K::Method | K::AssociatedFunction => stable_named_method_endpoint( - definitions, - source, - source_owner_name.unwrap_or(""), - source_name, - ), - K::Destructor => stable_named_destructor_endpoint( - definitions, - source, - source_owner_name.unwrap_or(source_name), - ), - K::Struct => stable_top_level_endpoint( - definitions, - source, - source_name, - StableDefinitionNamespace::Type, - StableDefinitionKind::Struct, - ), - K::Enum => stable_top_level_endpoint( - definitions, - source, - source_name, - StableDefinitionNamespace::Type, - StableDefinitionKind::Enum, - ), - K::ValueConst => stable_top_level_endpoint( - definitions, - source, - source_name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ValueConst, - ), - } -} - -fn stable_named_type_endpoint( - definitions: &BoundDefinitionSet, - event: &rue_air::DeclarationTypeDependencyEvent, -) -> Result { - let kind = match event.target_kind { - rue_air::DeclarationTypeDependencyTargetKind::Struct => StableDefinitionKind::Struct, - rue_air::DeclarationTypeDependencyTargetKind::Enum => StableDefinitionKind::Enum, - rue_air::DeclarationTypeDependencyTargetKind::ValueConst => { - return stable_top_level_endpoint( - definitions, - event.target_file, - &event.target_name, - StableDefinitionNamespace::Value, - StableDefinitionKind::ValueConst, - ); - } - }; - stable_top_level_endpoint( - definitions, - event.target_file, - &event.target_name, - StableDefinitionNamespace::Type, - kind, - ) -} - -fn stable_top_level_endpoint( - definitions: &BoundDefinitionSet, - file: u32, - name: &str, - namespace: StableDefinitionNamespace, - kind: StableDefinitionKind, -) -> Result { - let matches = definitions - .definitions() - .iter() - .filter(|record| { - record.declaration_span().file_id.index() == file - && record.stable_key().name() == name - && record.stable_key().namespace() == namespace - && record.stable_key().kind() == kind - && record.stable_key().owner().is_none() - }) - .collect::>(); - let [record] = matches.as_slice() else { - return Err(invalid_dependency_manifest( - "declaration-type dependency endpoint did not join exactly one stable definition", - )); - }; - Ok(record.stable_key().clone()) -} - -fn invalid_dependency_manifest(reason: &str) -> CompileErrors { - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - reason.to_owned(), - ))) -} - -fn semantic_diagnostic_input( - input: &CodegenInputDescriptor, - imports: CanonicalImportGraph, -) -> crate::ResolvedCodegenRevision { - crate::ResolvedCodegenRevision::new( - crate::ResolvedProgramRevision::new(input.semantic.clone(), imports), - input.opt_level, - ) -} - -fn programs_are_pointer_equivalent(left: &ParsedProgram, right: &ParsedProgram) -> bool { - left.source_revision() == right.source_revision() - && left.modules().len() == right.modules().len() - && left - .modules() - .iter() - .zip(right.modules()) - .all(|(left, right)| Arc::ptr_eq(left, right)) -} - -fn validate_accepted_read_manifest( - snapshot: &SourceSnapshot, - accepted_reads: &crate::AcceptedReadManifest, -) -> Result<(), CompileErrors> { - if accepted_reads.len() != snapshot.len() { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "accepted read manifest does not cover the staging source snapshot".into(), - ), - ))); - } - let entries = accepted_reads - .iter() - .map(|entry| (entry.module(), entry)) - .collect::>(); - if entries.len() != accepted_reads.len() { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput( - "accepted read manifest contains duplicate logical modules".into(), - ), - ))); - } - for source in snapshot.files() { - let module = snapshot - .module_id(source.file_id) - .expect("snapshot files have logical module IDs"); - let Some(entry) = entries.get(module) else { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput(format!( - "accepted read manifest is missing logical module {module}" - )), - ))); - }; - if entry.content_fingerprint() != crate::import_discovery::source_fingerprint(source.source) - { - return Err(CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput(format!( - "accepted read manifest content does not match logical module {module}" - )), - ))); - } - } - Ok(()) -} - -fn no_published_program() -> CompileErrors { - CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( - "frontend query session has no successful parsed program".to_string(), - ))) -} - -/// Successive fixed-point snapshots belong to one bounded discovery parse run -/// only when they preserve all prior source, manifest, context, and ledger -/// provenance. Any failed/closed/superseding attempt starts fresh accounting. -fn continues_discovery_lifecycle( - previous: &ImportDiscoveryRevisionArtifact, - snapshot: &SourceSnapshot, - context: &crate::ImportDiscoveryContext, - accepted_reads: &crate::AcceptedReadManifest, - carried_ledger: &crate::ImportObservationLedger, -) -> bool { - if previous.status != ImportDiscoveryRevisionStatus::Open || previous.context != *context { - return false; - } - let Some(program) = previous.program.as_deref() else { - return false; - }; - if program.root() != snapshot.source_revision().root() - || !program.modules_iter().all(|module| { - let file_id = module.file_id(); - snapshot.module_id(file_id) == Some(module.module_id()) - && snapshot.source_id(file_id) == Some(module.source_id()) - && snapshot.metadata().physical_path(file_id) == Some(module.physical_path()) - }) - { - return false; - } - if !previous - .accepted_reads - .iter() - .all(|entry| accepted_reads.contains_entry(entry)) - { - return false; - } - previous - .ledger - .iter() - .all(|observation| carried_ledger.get(observation.request()) == Some(observation)) -} - -#[cfg(test)] -impl CompilerSession { - /// Return the producer request that owns each currently retained ordinary - /// body terminal named by `names`. A missing declaration or a declaration - /// with no retained reached-body terminal is omitted. - /// - /// The scaling harness compares these stable provenance identities across - /// revisions to prove the exact recomputed body set. Equal work counts alone - /// cannot distinguish recomputing the intended consumers from recomputing - /// the same number of unrelated bodies. - pub(crate) fn retained_body_transaction_origins_for_test( - &self, - names: &[String], - ) -> BTreeMap { - let revision = self - .queries - .revisioned - .current_semantic_revision() - .expect("the acceptance corpus has a semantic revision"); - self.queries - .revisioned - .retained_body_transaction_origins_for_test(revision, names) - } - - /// Snapshot every retained body identity and its current observable - /// transaction for the correctness oracle. The map includes stale cache - /// identities with `None` when invalidation has made their terminal - /// unobservable at the current revision. - #[allow(dead_code)] - pub(crate) fn retained_body_identity_states_for_test( - &self, - options: &CompileOptions, - ) -> BTreeMap> { - let Some(revision) = self.queries.revisioned.current_semantic_revision() else { - return BTreeMap::new(); - }; - self.queries - .revisioned - .retained_body_identity_states_for_test( - revision, - crate::semantic_query_nucleus::SemanticQueryConfiguration { - target: options.target, - preview_features: StablePreviewFeatures::new(&options.preview_features), - }, - ) - } -} - -#[cfg(test)] -mod tests { - use std::{ - collections::{HashMap, HashSet}, - sync::Arc, - }; - - use rue_span::FileId; - use sha2::{Digest, Sha256}; - - use super::*; - use crate::{ - CanonicalSemanticFailurePhase, LinkerMode, ModuleId, OptLevel, PreviewFeature, - PreviewFeatures, SourceMetadata, SourceSnapshot, Target, - }; - - #[test] - fn phase_three_module_queries_close_the_exact_import_deletion_gate() { - let production = include_str!("session.rs") - .split("\n#[cfg(test)]\nmod tests {") - .next() - .unwrap(); - assert!(!production.contains("RUE-1024 DELETION GATE")); - let discovery = include_str!("import_discovery.rs"); - assert!(!discovery.contains("pub fn pending_requests(")); - let revisioned = include_str!("revisioned_query_database.rs"); - for family in [ - "compiler.parse-module", - "compiler.module-index", - "compiler.lookup-name", - "compiler.resolve-import", - "compiler.module-rir", - ] { - assert!( - revisioned.contains(family), - "missing canonical family {family}" - ); - } - assert!(!revisioned.contains("ImportModuleDemand")); - assert!(!revisioned.contains("compiler.import-module-frontier")); - assert_eq!( - revisioned - .matches("RUE-1026 DELETION GATE: this selected-revision compatibility") - .count(), - 0 - ); - let unstable = include_str!("unstable.rs"); - assert_eq!( - unstable - .matches("Full-plan host compatibility adapter. RUE-1026") - .count(), - 0 - ); - } - - #[test] - fn legacy_import_authority_is_one_explicit_compatibility_boundary() { - let discovery = include_str!("import_discovery.rs") - .split("\n#[cfg(test)]\nmod tests {") - .next() - .unwrap(); - let session = include_str!("session.rs") - .split("\n#[cfg(test)]\nmod tests {") - .next() - .unwrap(); - let gate = "RUE-1033 DELETION/REPLACEMENT GATE: retire the entire legacy supported"; - assert_eq!(discovery.matches(gate).count(), 1); - assert_eq!(session.matches(gate).count(), 0); - - // ADR-0061 keeps this legacy host-driven surface stable for now. Every - // listed item can participate in bypassing canonical - // begin/frontier/publish freshness or speculation authority, so the - // inventory must change atomically when RUE-1033 replaces it. - for (surface, declaration) in [ - ( - "ImportObservationStatus construction", - "pub enum ImportObservationStatus {", - ), - ( - "AcceptedImportSource::new", - "pub fn new(\n requested_path: impl Into>", - ), - ( - "ImportObservation::absent", - "pub fn absent(request: ImportDiscoveryRequest)", - ), - ( - "ImportObservation::accepted", - "pub fn accepted(\n request: ImportDiscoveryRequest", - ), - ( - "ImportObservation::failure", - "pub fn failure(\n request: ImportDiscoveryRequest", - ), - ( - "ImportObservationLedger::default", - "derive(Debug, Default)]\npub struct ImportObservationLedger", - ), - ( - "ImportObservationLedger::record", - "pub fn record(&mut self, observation: ImportObservation)", - ), - ( - "ImportDiscoveryPlan::groups", - "pub fn groups(&self) -> &[Arc<[ImportDiscoveryRequest]>]", - ), - ] { - assert_eq!( - discovery.matches(declaration).count(), - 1, - "RUE-1033 discovery bypass inventory drifted at {surface}" - ); - } - for (surface, declaration) in [ - ( - "CompilerSession::import_discovery_plan", - "pub fn import_discovery_plan(", - ), - ( - "CompilerSession::stage_import_discovery", - "pub fn stage_import_discovery(", - ), - ( - "CompilerSession::close_import_discovery", - "pub fn close_import_discovery(", - ), - ] { - assert_eq!( - session.matches(declaration).count(), - 1, - "RUE-1033 session bypass inventory drifted at {surface}" - ); - } - - let unstable = include_str!("unstable.rs"); - let begin = unstable - .split_once("pub fn begin_import_input_request(") - .unwrap() - .1 - .split_once(") -> crate::CompileResult") - .unwrap() - .0; - assert!(!begin.contains("ImportObservationLedger")); - assert!(!begin.contains("carried_ledger")); - } - - fn snapshot(entries: &[(u32, &str, &str, &str)], root: u32) -> SourceSnapshot { - let physical = entries - .iter() - .map(|(id, path, _, _)| (FileId::new(*id), (*path).to_owned())) - .collect::>(); - let logical = entries - .iter() - .map(|(id, _, logical, _)| (FileId::new(*id), (*logical).to_owned())) - .collect::>(); - let metadata = SourceMetadata::new(FileId::new(root), physical, logical).unwrap(); - SourceSnapshot::new( - metadata, - entries + ledger: crate::ImportObservationLedger, + successor: Option<(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, + ) -> Result, CompileErrors> { + let new_module_ids: Option> = successor.as_ref().map(|(_, delta)| { + delta .iter() - .map(|(id, _, _, text)| (FileId::new(*id), Arc::new((*text).to_owned()))) - .collect(), - ) - .unwrap() - } - - fn base() -> SourceSnapshot { - snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, - ) - } - - #[test] - fn absent_trusted_option_parks_the_rooted_attempt_with_exact_demand_and_anchor() { - // RUE-1112: a freestanding program whose reached `main` body uses a - // fallible intrinsic while NO trusted std module is present. The - // rooted attempt must park with exactly the `option.rue` demand, anchored - // on the demanding body (`main`), and must NOT run or publish any body - // transaction — the unsatisfied prerequisite stops the worklist before it - // enters `body_transaction`. - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }", - )], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - - let park = match session.semantic_or_toolchain_park(&CompileOptions::default()) { - SemanticParkOutcome::Parked(park) => park, - SemanticParkOutcome::Ready(_) => { - panic!("expected a trusted-toolchain park, got successful analysis") - } - SemanticParkOutcome::Errors(errors) => { - panic!("expected a trusted-toolchain park, got errors: {errors:?}") - } + .map(|revision| revision.module.clone()) + .collect() + }); + let new_modules: Option<&[crate::ModuleId]> = new_module_ids.as_deref(); + let open = self + .open_discovery + .as_deref() + .filter(|artifact| artifact.status == ImportDiscoveryRevisionStatus::Open) + .ok_or_else(|| CompileErrors::from(no_published_program()))? + .clone(); + let plan = open + .plan + .as_ref() + .expect("open discovery attempt retains its plan") + .clone(); + let program = open + .program + .as_ref() + .expect("open discovery attempt retains its program") + .clone(); + // A trusted-toolchain successor close carries the committed predecessor's + // closed graph and projects/reduces only the newly appended modules' + // occurrences. When no predecessor graph is retained it falls back to a + // full close so the committed graph is always complete. + let new_module_set: Option> = + new_modules.map(|modules| modules.iter().cloned().collect()); + let predecessor_graph = new_modules.and_then(|_| { + self.last_good_discovery_artifact() + .and_then(|artifact| artifact.graph.clone()) + }); + let narrow = match (new_module_set, predecessor_graph) { + (Some(set), Some(graph)) => Some((set, graph)), + _ => None, }; - - // Exact demand set: exactly the trusted std `Option` module. - let demands: Vec<&str> = park - .demands() - .iter() - .map(crate::TrustedToolchainModuleDemand::logical_path) - .collect(); - assert_eq!(demands, vec![crate::OPTION_MODULE_LOGICAL_PATH]); - - // Exact requester anchor: the demanding body's stable key (`main`). - assert_eq!(park.requesters().len(), 1); - let anchor = &park.requesters()[0]; - assert_eq!(anchor.name(), "main"); - assert_eq!(anchor.kind(), crate::StableDefinitionKind::Function); - - // No body transaction ran or published a terminal. - assert!( - !session.queries.revisioned.any_body_transaction_terminal(), - "the park must precede any body transaction", - ); - } - - #[test] - fn already_reached_parks_batch_into_one_park_with_unioned_demands_and_anchors() { - // RUE-1112 C2: two reached helper bodies demand different trusted modules - // (a: parse -> Option; b: read_line -> Option+StrBuf) while no trusted std - // is present. `main` reaches both, then the first to park must batch the - // remaining already-reached body: ONE park carrying the UNION of absent - // modules ([Option, StrBuf]) and BOTH requester anchors, so a single - // successor acquisition satisfies everything. - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn a() -> i32 { let _ = @parse_i64(\"1\"); 0 }\n\ - fn b() -> i32 { let _ = @read_line(); 0 }\n\ - fn main() -> i32 { a() + b() }", - )], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - - let park = match session.semantic_or_toolchain_park(&CompileOptions::default()) { - SemanticParkOutcome::Parked(park) => park, - SemanticParkOutcome::Ready(_) => panic!("expected a batched park, got ready analysis"), - SemanticParkOutcome::Errors(errors) => { - panic!("expected a batched park, got errors: {errors:?}") + // A successor projects only the delta occurrences, derived directly from + // the plan's delta segment — never by filtering the merged plan. + let roots = match &narrow { + Some(_) => plan.delta_roots(), + None => crate::ImportDemandRoots::whole_plan(&plan), + }; + let exact_groups = match self.queries.revisioned.current_import_revision() { + Some(revision) => match self + .queries + .revisioned + .exact_import_groups(revision, &roots) + { + Ok(groups) => groups, + Err(error) => { + let errors = CompileErrors::from(error); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + }, + None => { + #[cfg(test)] + { + plan.groups().to_vec() + } + #[cfg(not(test))] + { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "import closure requires a current compiler-published input revision" + .into(), + ), + ))); + } } }; - - // Union of absent modules across both already-reached bodies, sorted. - let demands: Vec<&str> = park - .demands() - .iter() - .map(crate::TrustedToolchainModuleDemand::logical_path) - .collect(); - assert_eq!( - demands, - vec![ - crate::OPTION_MODULE_LOGICAL_PATH, - crate::STRBUF_MODULE_LOGICAL_PATH - ] - ); - - // Both demanding bodies contribute a requester anchor. That both `a` and - // `b` appear proves neither transacted before the park — each was still - // pending and got projected into the one batch (`main`, which has no - // fallible intrinsic, does run its transaction first, as expected). - let anchors: std::collections::BTreeSet<&str> = - park.requesters().iter().map(|key| key.name()).collect(); - assert_eq!(anchors, std::collections::BTreeSet::from(["a", "b"])); - } - - // ---- RUE-1112: trusted-toolchain continuation + successor publication ---- - - fn continuation_std_context() -> crate::ImportDiscoveryContext { - crate::ImportDiscoveryContext::new(1, "/project", Some("/sdk"), "test-policy").unwrap() - } - - fn continuation_metadata() -> crate::FileMetadataFingerprint { - crate::FileMetadataFingerprint::new(10, 20, 30) - } - - /// Drive `root_source` to a canonical import-discovery close, then run the - /// rooted semantic attempt so its park atomically attaches the demanded-missing - /// set to the closed continuation. Returns the session (now holding an - /// AUTHORIZING continuation), its token, the empty closure-witness frontier, the - /// predecessor snapshot, its accepted reads, and the assembler ready to add - /// trusted leaves. Panics unless the attempt parked — the caller supplies a - /// reached-fallible-intrinsic root whose demand set is the acquisition batch. - /// - /// This exercises the real protocol (close → park → attach → mint): demand - /// authority is never seeded by direct field assignment, so a close whose - /// attempt never parks yields no token. - fn closed_continuation_for( - root_source: &str, - ) -> ( - CompilerSession, - ClosedDiscoveryContinuation, - crate::ImportDemandFrontier, - SourceSnapshot, - crate::AcceptedReadManifest, - crate::DiscoverySourceAssembler, - ) { - let ctx = continuation_std_context(); - let mut assembler = crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - Arc::new(root_source.to_owned()), - ) - .unwrap(); - let snapshot = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let mut session = CompilerSession::new(); - let revision = session - .begin_import_input_request(&snapshot, ctx.clone(), reads.clone()) - .unwrap(); - let plan = session - .stage_import_discovery( - &snapshot, - ctx.clone(), - reads.shared_slice(), - crate::ImportObservationLedger::default(), - ) - .unwrap(); - let roots = plan.demand_roots(); - let frontier = session - .import_demand_frontier_for_roots( - revision, - &plan, - crate::ImportDemandMode::Rooted, - &roots, - ) - .unwrap(); - assert!( - frontier.requests().is_empty(), - "a freestanding root closes with an empty frontier", - ); - let ledger = session.import_observation_ledger(revision).unwrap(); - session.close_import_discovery(ledger).unwrap(); - // A bare close is non-authorizing: no demand set has been attached yet. - assert!( - session.closed_discovery_continuation().is_none(), - "a close mints no token until a rooted park attaches a demanded set", - ); - // The rooted attempt parks; the park attaches its exact demanded-missing - // set to this closed state, making the continuation authorizing. - match session.semantic_or_toolchain_park(&CompileOptions::default()) { - SemanticParkOutcome::Parked(_) => {} - SemanticParkOutcome::Ready(_) => { - panic!("expected the reached fallible intrinsic to park the rooted attempt") - } - SemanticParkOutcome::Errors(errors) => { - panic!("expected a trusted-toolchain park, got errors: {errors:?}") + // The predecessor ledger portion was validated at the predecessor close; + // a successor close validates and reduces only the newly appended + // occurrences' observations. Those observations are gathered directly from + // the plan's delta groups (O(delta)), never by scanning the full carried + // ledger. The full `ledger` is still what the committed artifact carries. + let narrow_ledger = match &narrow { + Some(_) => { + let new_observations = plan + .delta_groups() + .iter() + .flat_map(|group| group.iter()) + .filter_map(|request| ledger.get(request).cloned()) + .collect::>(); + let mut filtered = crate::ImportObservationLedger::default(); + let mut record_error = None; + for observation in new_observations { + if let Err(error) = filtered.record(observation) { + record_error = Some(error); + break; + } + } + if let Some(error) = record_error { + let errors = CompileErrors::from(error); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + Some(filtered) } - } - let token = session - .closed_discovery_continuation() - .expect("an attached rooted park makes the closed continuation authorizing"); - (session, token, frontier, snapshot, reads, assembler) - } - - /// The common single-module case: a reached `@parse_i64` parks on exactly the - /// trusted std `Option` module. - fn closed_continuation() -> ( - CompilerSession, - ClosedDiscoveryContinuation, - crate::ImportDemandFrontier, - SourceSnapshot, - crate::AcceptedReadManifest, - crate::DiscoverySourceAssembler, - ) { - closed_continuation_for("fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }") - } - - fn add_trusted_option(assembler: &mut crate::DiscoverySourceAssembler) { - assembler - .add_explicit( - "/sdk/option.rue", - "/sdk/option.rue", - crate::PhysicalFileIdentity::new(2, 2), - continuation_metadata(), - Arc::new( - "pub fn Option(comptime T: type) -> type { enum { Some(T), None } }".to_owned(), - ), - ) - .unwrap(); - } - - fn add_trusted_strbuf(assembler: &mut crate::DiscoverySourceAssembler) { - assembler - .add_explicit( - "/sdk/strbuf.rue", - "/sdk/strbuf.rue", - crate::PhysicalFileIdentity::new(3, 3), - continuation_metadata(), - Arc::new("pub struct StrBuf { len: i64 }".to_owned()), - ) - .unwrap(); - } - - #[test] - fn trusted_successor_publishes_additive_leaf_in_same_generation() { - let (mut session, token, frontier, predecessor, _reads, mut assembler) = - closed_continuation(); - let predecessor_modules = predecessor.source_revision().modules().to_vec(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let successor_reads = assembler.accepted_read_manifest(); - - let delta = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, successor_reads) - .expect("a strictly-additive trusted successor publishes"); - // The publish mints an opaque delta authority bound to the appended set. - // Its module identities are private; the successor stage/close derive and - // verify them from the snapshot, so the host cannot edit them here. - let published = delta.revision(); - - // Same request generation as the predecessor close; the frontier round - // advances by one (a successor of that same observation epoch). - assert_eq!( - published.request_generation, - frontier.revision().request_generation - ); - assert_eq!( - published.frontier_round, - frontier.revision().frontier_round + 1 - ); - - // Every pre-existing module leaf is preserved byte-identical. Its exact - // ModuleRevision — and therefore its SourceId, the parse key — reappears in - // the successor, so no pre-existing module is re-read or reparsed across - // acquisition; only the trusted Option leaf is appended. - for old in &predecessor_modules { - assert!( - successor.source_revision().modules().contains(old), - "pre-existing module {old:?} must be preserved byte-identical", + None => None, + }; + let check_ledger = narrow_ledger.as_ref().unwrap_or(&ledger); + if let Err(error) = + crate::import_discovery::validate_exact_import_ledger(&exact_groups, check_ledger) + { + let errors = CompileErrors::from(error); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, ); + return Err(errors); } - assert_eq!( - successor.source_revision().modules().len(), - predecessor_modules.len() + 1, + if !crate::import_discovery::exact_import_pending_requests(&exact_groups, check_ledger) + .is_empty() + { + let errors = + CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( + "import discovery ledger is incomplete; the attempted revision cannot close" + .into(), + ))); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + let diagnostics = crate::import_discovery::exact_import_diagnostics( + &program, + &exact_groups, + check_ledger, ); - } + if crate::import_discovery::exact_import_has_failures(&exact_groups, check_ledger) { + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &diagnostics, + ); + return Err(diagnostics); + } - #[test] - fn trusted_successor_reused_token_is_rejected() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - // The first publish consumes the single-use token. - session - .publish_trusted_toolchain_successor( - token.clone(), - &frontier, - &successor, - reads.clone(), + // A successor shares the committed predecessor's module-resolution table + // by reference and appends only the delta modules (looked up by identity), + // so the complete table is never reconstructed or re-sorted. A full close + // builds the whole table. + let resolution_build = match &narrow { + Some((set, predecessor)) => { + let delta: Vec = set + .iter() + .filter_map(|module_id| program.module(module_id)) + .map(|module| crate::ModuleResolutionInput { + module: module.module_id().clone(), + physical_path: Arc::from(module.physical_path()), + }) + .collect(); + crate::ModuleResolutionInputs::extend_successor( + &predecessor.input().resolution, + delta, + ) + } + None => crate::ModuleResolutionInputs::new( + program.root().clone(), + program + .modules() + .iter() + .map(|module| crate::ModuleResolutionInput { + module: module.module_id().clone(), + physical_path: Arc::from(module.physical_path()), + }) + .collect(), + ), + }; + let resolution = match resolution_build { + Ok(resolution) => resolution, + Err(error) => { + let errors = CompileErrors::from(error); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + }; + let input = ImportGraphInputDescriptor { + sources: program.source_revision().clone(), + resolution, + std_dir: open.context.std_root().map(Arc::from), + }; + // Reduce only the projected occurrences: the whole plan for a full close, + // or exactly the newly appended modules' occurrences for a trusted-toolchain + // successor. `reduced` therefore holds the new records in successor mode. + let reduced = match crate::import_discovery::reduce_exact_import_graph( + program.root().clone(), + &exact_groups, + check_ledger, + &open.accepted_reads, + ) { + Ok(graph) => graph, + Err(error) => { + let errors = CompileErrors::from(error); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + }; + self.import_close_records_reduced = self + .import_close_records_reduced + .saturating_add(reduced.records().len() as u64); + // In successor mode, merge the new records into the committed predecessor's + // closed graph and validate incrementally (the predecessor topology is + // carried, never re-walked). A full close reduces and validates the whole + // graph directly. + let (reduced, validation) = match &narrow { + Some((set, predecessor)) => { + // The reduction produced only the delta records; build the + // successor graph by structurally sharing the predecessor's record + // segment (no predecessor record is copied or re-sorted) and + // validate only the delta against the carried predecessor result. + let new_records = reduced.records().to_vec(); + let merged = crate::CanonicalImportGraph::from_additive_successor( + program.root().clone(), + predecessor.graph(), + new_records.clone(), + ); + let validation = crate::validate_additive_successor( + predecessor.validation(), + &new_records, + &input.resolution, + set, + ); + (merged, validation) + } + None => { + let validation = validate_canonical_import_graph(&reduced, &input.resolution); + (reduced, validation) + } + }; + let graph = Arc::new(CanonicalImportGraphOutput { + input: input.clone(), + graph: reduced, + validation, + }); + let resolution_only = graph.validation().problems().iter().all(|problem| { + matches!( + problem, + crate::CanonicalImportGraphProblem::MissingResolution { .. } + | crate::CanonicalImportGraphProblem::AmbiguousResolution { .. } ) - .unwrap(); - // Reusing it finds no outstanding continuation. - let err = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .unwrap_err(); - assert!( - err.first().unwrap().to_string().contains("already used"), - "{err:?}", - ); - } - - #[test] - fn trusted_successor_stale_token_is_rejected() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - // Simulate a newer close superseding this token: advance the outstanding - // state's nonce so the presented token no longer matches (stale). - session.next_continuation_nonce += 7; - session.continuation.as_mut().unwrap().nonce = session.next_continuation_nonce; - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let err = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .unwrap_err(); - assert!( - err.first().unwrap().to_string().contains("stale"), - "{err:?}" - ); - } + }); + if !resolution_only { + let mut errors = diagnostics; + errors.push(CompileError::without_span(ErrorKind::InvalidCompilerInput( + "import discovery produced a structurally invalid canonical graph".into(), + ))); + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + if !graph.validation().is_valid() || !diagnostics.is_empty() { + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + Some(graph), + &diagnostics, + ); + return Err(diagnostics); + } - #[test] - fn trusted_successor_new_request_invalidates_the_token() { - let (mut session, token, frontier, predecessor, reads, mut assembler) = - closed_continuation(); - // A fresh import-input request invalidates any outstanding continuation. - session - .begin_import_input_request(&predecessor, continuation_std_context(), reads) - .unwrap(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let successor_reads = assembler.accepted_read_manifest(); - let err = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, successor_reads) - .unwrap_err(); - assert!( - err.first().unwrap().to_string().contains("already used"), - "{err:?}", + let adoption = self.adopt_discovery_program_for_presentation( + &open.snapshot, + program.clone(), + open.parse_work, + successor.is_some(), + open.successor_parse.clone(), ); - } - - #[test] - fn trusted_successor_mutated_predecessor_is_rejected() { - let (mut session, token, frontier, _pred, _reads, _assembler) = closed_continuation(); - // A successor whose pre-existing root content differs is a mutated - // predecessor: source evolution must be strictly additive. - let ctx = continuation_std_context(); - let mut other = crate::DiscoverySourceAssembler::new( - ctx, - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - Arc::new("fn main() -> i32 { 1 }".to_owned()), - ) - .unwrap(); - add_trusted_option(&mut other); - let successor = other.snapshot().unwrap(); - let reads = other.accepted_read_manifest(); - let err = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .unwrap_err(); - assert!( - err.first() - .unwrap() - .to_string() - .contains("strictly additive"), - "{err:?}", + if let Err(errors) = adoption.into_result() { + self.publish_failed_import_attempt( + open, + plan, + ledger, + successor.as_ref(), + ImportDiscoveryRevisionStatus::ClosedAttempted, + None, + &errors, + ); + return Err(errors); + } + let diagnostic_snapshot = self.publish_import_diagnostics( + &open.snapshot, + Some(open.context.clone()), + Some(plan), + ledger.clone(), + open.accepted_reads.clone(), + &diagnostics, ); + let artifact = Arc::new(ImportDiscoveryRevisionArtifact { + status: ImportDiscoveryRevisionStatus::ClosedValid, + ledger, + graph: Some(graph.clone()), + diagnostics, + diagnostic_snapshot: Some(diagnostic_snapshot), + ..open + }); + self.queries.record_discovery_attempt(artifact.clone()); + self.open_discovery = None; + // A committed close is a lineage boundary: additions recorded before it + // belong to the closed graph, so the recorded-additions lineage resets. + self.queries.revisioned.clear_lineage_additions(); + // Record the closed state for a possible trusted-toolchain continuation + // (RUE-1112), but only when the canonical begin/frontier/publish + // protocol produced a current import-input revision — legacy embedders + // that bypass it get no continuation. The state retains everything the + // successor verification needs (predecessor snapshot, context, accepted + // reads, carried ledger) so the check is record-only, never filesystem. + // + // The closed state is deliberately NON-AUTHORIZING here (`attached_demands` + // is `None`): a close by itself mints no token and authorizes no successor. + // Only a subsequent rooted semantic park attaches its exact missing-demand + // set to this same state, so a later close whose attempt never parks can + // never inherit an earlier park's authority. + self.continuation = self + .queries + .revisioned + .current_import_revision() + .map(|revision| { + self.next_continuation_nonce += 1; + ContinuationState { + nonce: self.next_continuation_nonce, + revision, + snapshot: artifact.snapshot().clone(), + accepted_reads: artifact.accepted_read_manifest().clone(), + ledger: artifact.ledger().clone(), + attached_demands: None, + } + }); + Ok(artifact) } - #[test] - fn trusted_successor_arbitrary_module_is_rejected() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - // StrBuf is a trusted module the park did NOT demand here (the reached - // `@parse_i64` parks on Option only), so the added set {StrBuf} does not - // equal the demanded set {Option} and may not ride in on this continuation. - add_trusted_strbuf(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let err = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .unwrap_err(); - assert!( - err.first() - .unwrap() - .to_string() - .contains("must equal the rooted park's demanded missing set"), - "{err:?}", - ); + /// Mint the trusted-toolchain continuation token for the current successful + /// import-discovery close, if one is outstanding AND authorizing (RUE-1112). + /// A closed state becomes authorizing only once a rooted semantic park + /// has attached its exact missing-demand set; a close whose attempt is ready + /// (or never parked) mints no token. The token is opaque and single-use; the + /// host hands it back to [`Self::publish_trusted_toolchain_successor`]. + pub(crate) fn closed_discovery_continuation(&self) -> Option { + self.continuation + .as_ref() + .filter(|state| state.attached_demands.is_some()) + .map(|state| ClosedDiscoveryContinuation { + session: self.identity.clone(), + nonce: state.nonce, + revision: state.revision, + }) } - #[test] - fn trusted_successor_ready_close_is_non_authorizing() { - // A close whose rooted semantic attempt is READY (no fallible intrinsic, - // no park) attaches no demanded set, so the closed continuation mints no - // token. Demand authority lives only in an attached park, so a ready close - // can never inherit an earlier park's demand set and admit an uninvited - // trusted leaf. - let ctx = continuation_std_context(); - let mut assembler = crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - Arc::new("fn main() -> i32 { 0 }".to_owned()), - ) - .unwrap(); - let snapshot = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let mut session = CompilerSession::new(); - let revision = session - .begin_import_input_request(&snapshot, ctx.clone(), reads.clone()) - .unwrap(); - let plan = session - .stage_import_discovery( - &snapshot, - ctx.clone(), - reads.shared_slice(), - crate::ImportObservationLedger::default(), - ) - .unwrap(); - let roots = plan.demand_roots(); - let _frontier = session - .import_demand_frontier_for_roots( - revision, - &plan, - crate::ImportDemandMode::Rooted, - &roots, - ) - .unwrap(); - let ledger = session.import_observation_ledger(revision).unwrap(); - session.close_import_discovery(ledger).unwrap(); - // The rooted attempt is ready: no park, so no demanded set is attached. - assert!(matches!( - session.semantic_or_toolchain_park(&CompileOptions::default()), - SemanticParkOutcome::Ready(_) - )); - assert!( - session.closed_discovery_continuation().is_none(), - "a ready close is non-authorizing and mints no continuation token", - ); - } + /// Publish exactly one strictly-additive trusted-toolchain successor on the + /// continuation's closed revision (RUE-1112). + /// + /// The host has already done all filesystem work through the B4-hardened + /// path — read each demanded module, checked containment/manifest/stable-read + /// provenance, assembled the successor snapshot and accepted-read records. + /// This verifies that work purely from records (no filesystem access) by + /// diffing the successor against the continuation's predecessor, then + /// publishes in the SAME request generation carrying the predecessor ledger + /// unchanged. The added leaves carry no observation in the predecessor + /// ledger yet; discovery of the `@import` edges they introduce (a trusted + /// leaf such as `strbuf.rue` imports `option.rue`/`arraybuf.rue`/`rawbuf.rue`) + /// is the driver's subsequent re-close, which roots its frontier only in + /// these new leaves. + /// + /// Returns the successor revision together with the exact set of module IDs + /// it appended (the verified `added == demanded` set). The re-close uses that + /// set as the sole discovery frontier roots, so the predecessor import + /// topology is never re-rooted or re-resolved. + pub(crate) fn publish_trusted_toolchain_successor( + &mut self, + token: ClosedDiscoveryContinuation, + issued_frontier: &crate::ImportDemandFrontier, + successor: &SourceSnapshot, + accepted_reads: crate::AcceptedReadManifest, + ) -> Result { + let reject = |message: &str| { + CompileErrors::from(crate::CompileError::without_span( + rue_error::ErrorKind::InvalidCompilerInput(format!( + "trusted-toolchain successor rejected: {message}" + )), + )) + }; - #[test] - fn trusted_successor_partial_batch_is_rejected_without_consuming_token() { - // A reached `@read_line` parks on BOTH Option and StrBuf. A successor that - // adds only Option is a partial batch — added {Option} does not equal the - // demanded {Option, StrBuf} — so it is rejected. A rejection never consumes - // the single-use token, so completing the batch and retrying with the same - // token then publishes. - let (mut session, token, frontier, _pred, _reads, mut assembler) = - closed_continuation_for("fn main() -> i32 { let _ = @read_line(); 0 }"); - add_trusted_option(&mut assembler); - let partial = assembler.snapshot().unwrap(); - let partial_reads = assembler.accepted_read_manifest(); - let err = session - .publish_trusted_toolchain_successor(token.clone(), &frontier, &partial, partial_reads) - .unwrap_err(); - assert!( - err.first() - .unwrap() - .to_string() - .contains("must equal the rooted park's demanded missing set"), - "{err:?}", - ); - // The token survived the rejection; completing the two-module batch and - // retrying publishes with the SAME token. - add_trusted_strbuf(&mut assembler); - let full = assembler.snapshot().unwrap(); - let full_reads = assembler.accepted_read_manifest(); - session - .publish_trusted_toolchain_successor(token, &frontier, &full, full_reads) - .expect("the completed two-module batch publishes with the un-consumed token"); - } + // Same session. + if !Arc::ptr_eq(&token.session, &self.identity) { + return Err(reject("continuation token belongs to a different session")); + } + // Token current + unused. Peek without consuming so a rejected batch + // leaves the token valid for a corrected retry; only a successful publish + // consumes it (a reused token then finds no outstanding state). + let state = match self.continuation.as_ref() { + Some(state) if token.nonce == state.nonce && token.revision == state.revision => { + state.clone() + } + Some(_) => { + return Err(reject( + "continuation token is stale (superseded by a newer close or request)", + )); + } + None => { + return Err(reject( + "no outstanding closed-discovery continuation; the token was already used or invalidated", + )); + } + }; - #[test] - fn trusted_successor_altered_predecessor_provenance_is_rejected() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let full_reads = assembler.accepted_read_manifest(); - // Drop the predecessor root's accepted-read provenance, keeping only the - // added leaf's: the old provenance is no longer byte-identical. - let tampered: Vec<_> = full_reads - .iter() - .filter(|entry| entry.module().is_trusted_standard_library()) - .cloned() - .collect(); - let err = session - .publish_trusted_toolchain_successor( - token, - &frontier, - &successor, - crate::AcceptedReadManifest::from_entries(tampered), - ) - .unwrap_err(); - assert!( - err.first() - .unwrap() - .to_string() - .contains("altered or removed"), - "{err:?}", - ); - } + // The closure witness: the empty rooted frontier of the token's closed + // revision. Only a genuinely-closed predecessor may continue. + if issued_frontier.mode() != crate::ImportDemandMode::Rooted { + return Err(reject("the closure witness frontier must be rooted")); + } + if issued_frontier.revision() != state.revision { + return Err(reject( + "the closure witness frontier does not belong to the continuation's revision", + )); + } + if !issued_frontier.requests().is_empty() { + return Err(reject( + "the closure witness frontier is not empty; the predecessor did not close", + )); + } - /// A successor-delta capability minted by one session cannot authorize a - /// successor stage on a different session: the delta is bound to its issuing - /// session, so a cross-session value is rejected without staging anything. - #[test] - fn successor_delta_from_another_session_is_rejected() { - let (mut issuer, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let delta = issuer - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads.clone()) - .expect("a strictly-additive successor publishes"); + // Same compilation root (the context/read policy is carried unchanged + // into the successor below). + if successor.source_revision().root() != state.snapshot.source_revision().root() { + return Err(reject("the successor changed the compilation root")); + } - let mut other = CompilerSession::new(); - let err = other.stage_import_discovery_successor(&delta).unwrap_err(); - assert!( - err.first() - .unwrap() - .to_string() - .contains("different session"), - "{err:?}", - ); - } + // Strict additive source evolution: every predecessor module revision + // must appear byte-identical in the successor; the additions are exactly + // the new leaves. + let old_modules: std::collections::BTreeSet<&crate::ModuleRevision> = + state.snapshot.source_revision().modules().iter().collect(); + let new_modules: std::collections::BTreeSet<&crate::ModuleRevision> = + successor.source_revision().modules().iter().collect(); + if !old_modules.is_subset(&new_modules) { + return Err(reject( + "a predecessor module revision was mutated or removed (source evolution must be strictly additive)", + )); + } + let additions: Vec<&crate::ModuleRevision> = + new_modules.difference(&old_modules).copied().collect(); + if additions.is_empty() { + return Err(reject( + "a trusted-toolchain successor must add at least one leaf", + )); + } - /// A successor-delta capability is single-generation: a new import-input - /// request invalidates it, so a stale delta can neither stage nor close. - #[test] - fn stale_successor_delta_cannot_stage() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let delta = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads.clone()) - .expect("a strictly-additive successor publishes"); + // Every predecessor accepted-read entry must appear byte-identical in the + // successor manifest (altered old provenance rejected). + let new_reads: std::collections::HashSet<&crate::AcceptedReadManifestEntry> = + accepted_reads.iter().collect(); + for old in state.accepted_reads.iter() { + if !new_reads.contains(old) { + return Err(reject( + "a predecessor accepted-read provenance entry was altered or removed", + )); + } + } - // A fresh observation generation invalidates the outstanding delta. - session - .begin_import_input_request(&successor, continuation_std_context(), reads.clone()) - .unwrap(); - let err = session - .stage_import_discovery_successor(&delta) - .unwrap_err(); - assert!( - err.first() - .unwrap() - .to_string() - .contains("no outstanding successor-delta authority"), - "{err:?}", - ); - } + // Demand authority lives only in the attached park set. A close whose + // rooted attempt never parked is non-authorizing: it may not consume the + // token or admit any leaf, so a later ready close can never reuse an + // earlier park's demands. + let Some(attached_demands) = state.attached_demands.as_ref() else { + return Err(reject( + "the closed continuation is not authorizing; no rooted semantic park has attached a demanded-module set", + )); + }; - /// The successor parse terminal is a REAL runtime query dependent of the - /// exact predecessor parse terminal — the graph carries the - /// successor-after-predecessor edge — and the successor close re-selects - /// the staged terminal itself: same terminal identity, no second parse - /// dispatch, and no second empty-extension publication. - #[test] - fn successor_close_reuses_the_staged_terminal_with_a_predecessor_edge() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - let predecessor_terminal = session - .selected_parse_terminal() - .expect("the committed close selects its staged parse terminal"); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let delta = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .expect("a strictly-additive successor publishes"); - session - .stage_import_discovery_successor(&delta) - .expect("the successor stages"); - let staged_terminal = session - .selected_parse_terminal() - .expect("the successor stage selects its parse terminal"); - assert!( - !Arc::ptr_eq(&predecessor_terminal, &staged_terminal), - "the successor stage computes its own terminal" - ); - // (a) The successor terminal observes the exact predecessor parse - // terminal as a runtime query dependency — the FULL captured identity - // (node, incarnation, AND stamp), not an equivalent replacement under - // the same display node — so red/green validation and leases flow - // successor-after-predecessor through the graph. - let observation = staged_terminal - .dependencies() + // Every addition is a trusted standard-library leaf with well-formed + // accepted-read provenance in the successor manifest. + for addition in &additions { + if !addition.module.is_trusted_standard_library() { + return Err(reject( + "an added leaf is not a trusted standard-library module", + )); + } + if !accepted_reads + .iter() + .any(|entry| entry.module() == &addition.module) + { + return Err(reject( + "an added trusted leaf has no accepted-read provenance", + )); + } + } + + // The successor's added module-ID set must EQUAL the park's demanded + // missing set — set equality, not per-member membership. This enforces + // the one-park/one-batched-successor contract in both directions: an + // arbitrary or uninvited module (added ⊄ demanded) is rejected, and a + // partial batch that omits a demanded member (demanded ⊄ added) is + // rejected WITHOUT consuming the single-use token (the peek above only + // consumes on the successful publish below). + let demanded: std::collections::BTreeSet = attached_demands .iter() - .find(|dependency| dependency.node == *predecessor_terminal.node()) - .unwrap_or_else(|| { - panic!( - "the successor terminal must depend on the exact predecessor parse terminal: {:?}", - staged_terminal.dependencies(), - ) - }); - assert_eq!( - observation.incarnation, - predecessor_terminal.node_incarnation(), - "the dependency must carry the captured terminal's exact node incarnation" - ); - assert_eq!( - observation.stamp, - predecessor_terminal.stamp(), - "the dependency must carry the captured terminal's exact stamp" - ); - // That the adoption touched no predecessor content-key Hash/Eq is - // proven mechanically by the rue-query frozen-key regression - // (`adoption_never_hashes_or_compares_the_predecessor_key`). - // (b) The close re-selects the staged terminal itself: identical - // terminal identity, no parse dispatch, and no second publication. - let dispatched = session.parse_modules_dispatched(); - let materialized = session.parse_sources_materialized(); - session - .close_import_discovery_successor(&delta) - .expect("the successor closes"); - let adopted_terminal = session - .selected_parse_terminal() - .expect("the successor close selects the staged parse terminal"); - assert!( - Arc::ptr_eq(&staged_terminal, &adopted_terminal), - "the successor close must re-select the exact staged parse terminal" - ); - assert_eq!( - session.parse_modules_dispatched(), - dispatched, - "the successor close dispatches no parse work" - ); - assert_eq!( - session.parse_sources_materialized(), - materialized, - "the successor close materializes no whole-program projection" - ); + .map(|demand| demand.trusted_module_id()) + .collect::>() + .map_err(CompileErrors::from)?; + let added: std::collections::BTreeSet = additions + .iter() + .map(|addition| addition.module.clone()) + .collect(); + if added != demanded { + return Err(reject( + "the successor's added trusted modules must equal the rooted park's demanded missing set exactly (one park, one batched successor)", + )); + } + + // Publish the strictly-additive successor in the SAME request generation + // as a sparse overlay over the predecessor view: the carried ledger and + // topology are inherited unchanged, only the verified added leaves' + // source/provenance leaves are published, and the overlay re-derives the + // additions from the published parent view (they must equal `added`). + let published = self + .queries + .revisioned + .publish_trusted_successor_view( + state.revision, + successor, + accepted_reads, + state.ledger.clone(), + &added, + state.revision.frontier_round + 1, + ) + .map_err(CompileErrors::from)?; + // Consume the single-use continuation only on success. + self.continuation = None; + // Mint the opaque successor-delta authority from the VERIFIED `added` + // set (equal to the park's demanded missing set). `BTreeSet` iteration is + // sorted, so the appended roots are deterministic. The host receives only + // this opaque value; it cannot inspect or edit the module identities. + let appended: Arc<[crate::ModuleId]> = added.into_iter().collect::>().into(); + self.next_continuation_nonce += 1; + let nonce = self.next_continuation_nonce; + self.successor_delta_nonce = Some(nonce); + Ok(TrustedSuccessorDelta { + session: self.identity.clone(), + nonce, + revision: published, + appended, + }) } - /// A strictly-additive successor adoption must leave the predecessor's - /// immutable source leaf live: retained frontend terminals that correctly - /// depend on it (however many variants are prewarmed) stay valid, and the - /// acquisition contributes ZERO dependency-graph invalidation events — - /// the successor becomes current without walking or invalidating the - /// predecessor's retained downstream. - #[test] - fn successor_adoption_invalidates_no_retained_frontend_variants() { - let acquisition_invalidations = |prewarm_retained_downstream: bool| -> u64 { - let (mut session, token, frontier, _pred, _reads, mut assembler) = - closed_continuation(); - if prewarm_retained_downstream { - // Retain additional terminals depending on the predecessor's - // source leaf. Semantic — and the definition/manifest variants - // that observe it — cannot complete on this predecessor (the - // reached fallible intrinsic parks semantic until - // acquisition), so the retained downstream of the leaf is the - // pre-semantic tier: merged RIR and canonical import - // diagnostics. - session - .rir() - .expect("pre-semantic RIR completes on the parked predecessor"); - session - .import_diagnostics() - .expect("import diagnostics retain on the closed predecessor"); - } - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let delta = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .expect("a strictly-additive successor publishes"); - let before = session.frontend_query_invalidations(); - session - .stage_import_discovery_successor(&delta) - .expect("the successor stages"); - session - .close_import_discovery_successor(&delta) - .expect("the successor closes"); - session.frontend_query_invalidations() - before - }; - let bare = acquisition_invalidations(false); - let prewarmed = acquisition_invalidations(true); - assert_eq!( - bare, 0, - "additive successor adoption must not invalidate retained frontend terminals" - ); - assert_eq!( - prewarmed, 0, - "additive successor adoption must not invalidate retained frontend terminals regardless of how much retained downstream depends on the predecessor leaf" + fn publish_failed_import_attempt( + &mut self, + open: ImportDiscoveryRevisionArtifact, + plan: crate::ImportDiscoveryPlan, + ledger: crate::ImportObservationLedger, + _successor: Option<&(crate::ImportInputRevision, Arc<[crate::ModuleRevision]>)>, + status: ImportDiscoveryRevisionStatus, + graph: Option>, + errors: &CompileErrors, + ) -> Arc { + debug_assert_ne!(status, ImportDiscoveryRevisionStatus::ClosedValid); + let diagnostic_snapshot = self.publish_import_diagnostics( + &open.snapshot, + Some(open.context.clone()), + Some(plan), + ledger.clone(), + open.accepted_reads.clone(), + errors, ); + let artifact = Arc::new(ImportDiscoveryRevisionArtifact { + status, + ledger, + graph, + diagnostics: errors.clone(), + diagnostic_snapshot: Some(diagnostic_snapshot), + ..open + }); + self.queries.record_discovery_attempt(artifact.clone()); + self.open_discovery = None; + artifact } - /// A successor-delta capability outstanding across an intervening source or - /// presentation update is invalidated: the update replaced the retained - /// parse artifact the successor would extend, so the stale capability can - /// neither stage nor close — a mixed parsed program (foreign retained - /// modules under the successor's claimed source revision) is never - /// produced. - #[test] - fn intervening_presentation_update_invalidates_successor_delta() { - let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); - add_trusted_option(&mut assembler); - let successor = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let delta = session - .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) - .expect("a strictly-additive successor publishes"); + fn require_closed_discovery(&self) -> Result<(), CompileErrors> { + if self + .discovery_attempt_artifact() + .is_some_and(|attempt| attempt.status != ImportDiscoveryRevisionStatus::ClosedValid) + { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "semantic and dependency queries require a closed valid discovery revision" + .into(), + ), + ))); + } + Ok(()) + } + pub(crate) fn work(&self) -> &CompilerSessionWork { + self.metrics.work() + } + /// Return an owned snapshot of explicitly unstable compiler metrics. + /// + /// The snapshot cannot be installed back into this or another session and + /// therefore grants no access to query ownership or invalidation state. + pub fn unstable_metrics(&self) -> crate::unstable::MetricsSnapshot { + crate::unstable::MetricsSnapshot::new(self.metrics.work().clone()) + } + /// Diagnostic snapshot from the most recently attempted query, whether it + /// succeeded or failed. + pub fn latest_diagnostics(&self) -> Option<&Arc> { + self.diagnostics.latest() + } + /// Most recently queried diagnostic snapshot with no errors. + pub fn latest_successful_diagnostics(&self) -> Option<&Arc> { + self.diagnostics.latest_successful() + } + /// Most recent successful semantic diagnostic snapshot. + /// + /// Syntax or semantic failures never replace this last-good semantic + /// baseline. A caller may clone the returned `Arc` to pin it independently + /// of later session eviction. + pub fn last_good_semantic_diagnostics(&self) -> Option<&Arc> { + self.diagnostics.last_good_semantic() + } - // An intervening presentation update installs a successful parse of a - // DIFFERENT snapshot (unrelated content and file order). - let foreign = snapshot( - &[ - (2, "/q/aux.rue", "aux.rue", "pub fn v() -> i32 { 2 }"), - (1, "/q/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - ], - 1, - ); - session - .update_for_presentation(&foreign) - .into_result() - .expect("the foreign presentation update parses"); + /// Look up the currently selected, or otherwise most recently indexed, + /// diagnostic batch matching a source-attempt and public query stage. + /// + /// Canonical and presentation-ordered producer attempts can share the same + /// public stage. When the current selection matches, this returns that + /// exact batch; otherwise it returns the most recently indexed match. + /// Clone the `Arc` when the artifact must outlive index eviction. + #[cfg(test)] + pub(crate) fn most_recent_diagnostics_for( + &self, + source: &SourceSnapshot, + stage: &FrontendDiagnosticIdentity, + ) -> Option<&Arc> { + self.diagnostics.find(source, stage) + } + + /// Compatibility name for [`Self::most_recent_diagnostics_for`]. + /// + /// This is not an exact lookup when canonical and presentation provenance + /// share a public stage; it follows the selection contract documented by + /// `most_recent_diagnostics_for`. + #[cfg(test)] + #[allow(dead_code)] + pub(crate) fn diagnostics_for( + &self, + source: &SourceSnapshot, + stage: &FrontendDiagnosticIdentity, + ) -> Option<&Arc> { + self.most_recent_diagnostics_for(source, stage) + } + + fn publish_diagnostics( + &mut self, + source: &SourceSnapshot, + stage: FrontendDiagnosticIdentity, + errors: Option<&CompileErrors>, + warnings: &[CompileWarning], + ) -> Arc { + let provenance = match &stage { + FrontendDiagnosticIdentity::Syntax => self + .batch_diagnostic_order + .as_ref() + .map_or(DiagnosticAttemptProvenance::Canonical, |order| { + DiagnosticAttemptProvenance::Presentation(order.clone()) + }), + FrontendDiagnosticIdentity::Merge if errors.is_some() => self + .batch_diagnostic_order + .as_ref() + .map_or(DiagnosticAttemptProvenance::Canonical, |order| { + DiagnosticAttemptProvenance::Presentation(order.clone()) + }), + FrontendDiagnosticIdentity::Merge => DiagnosticAttemptProvenance::Canonical, + FrontendDiagnosticIdentity::Import(_) + | FrontendDiagnosticIdentity::Rir(_) + | FrontendDiagnosticIdentity::Semantic(_) => DiagnosticAttemptProvenance::Canonical, + }; + if let Some(existing) = self + .diagnostics + .find_exact(source, &stage, &provenance) + .cloned() + { + self.metrics.diagnostic_reuse(); + self.diagnostics.select_snapshot(&existing); + self.refresh_retention_metrics(); + return existing; + } + let invalidated_previous = self.diagnostics.latest().is_some(); + let snapshot = Arc::new(FrontendDiagnosticSnapshot { + source: source.clone(), + stage, + provenance, + errors: errors + .map(|errors| errors.iter().cloned().collect::>()) + .unwrap_or_default() + .into(), + warnings: warnings.to_vec().into(), + }); + self.metrics.diagnostic_publication(invalidated_previous); + self.refresh_retention_metrics(); + snapshot + } - let stage_err = session - .stage_import_discovery_successor(&delta) - .unwrap_err(); - assert!( - stage_err - .first() - .unwrap() - .to_string() - .contains("no outstanding successor-delta authority"), - "{stage_err:?}", - ); - let close_err = session - .close_import_discovery_successor(&delta) - .unwrap_err(); - assert!( - close_err - .first() - .unwrap() - .to_string() - .contains("no outstanding successor-delta authority"), - "{close_err:?}", - ); + fn reuse_diagnostics(&mut self, snapshot: Arc) { + self.metrics.diagnostic_reuse(); + self.diagnostics.select_snapshot(&snapshot); + self.refresh_retention_metrics(); } - /// Substituted snapshots, contexts, provenance manifests, and ledgers are - /// INEXPRESSIBLE at the successor stage/close: those APIs consume only the - /// compiler-published view and the opaque capability. The one remaining host - /// input surface on a same-generation lineage is the observation-batch - /// publication, so the tampering regressions below attack through it; the - /// overlay publication re-derives and justifies every addition, rejecting - /// each attack before anything is published. - /// - /// Run one tampered batch publication against a closed lineage whose rooted - /// frontier witness is empty, returning the rejection text. - fn tampered_batch_error( - build: impl FnOnce(&crate::ImportDiscoveryContext) -> crate::DiscoverySourceAssembler, - ) -> String { - let (mut session, _token, frontier, _pred, _reads, _assembler) = closed_continuation(); - let ctx = continuation_std_context(); - let mut tampered = build(&ctx); - let snapshot = tampered.snapshot().unwrap(); - let reads = tampered.accepted_read_manifest(); - session - .publish_import_observation_batch(&frontier, &snapshot, reads, Vec::new()) - .unwrap_err() - .to_string() + fn publish_import_diagnostics( + &mut self, + source: &SourceSnapshot, + context: Option, + plan: Option, + ledger: crate::ImportObservationLedger, + accepted_reads: crate::AcceptedReadManifest, + errors: &CompileErrors, + ) -> Arc { + let input = ImportDiagnosticInputDescriptor { + source: source.source_revision().clone(), + context, + plan, + ledger, + accepted_reads, + }; + self.publish_diagnostics( + source, + FrontendDiagnosticIdentity::Import(input), + Some(errors), + &[], + ) } - /// A batch cannot INJECT a module: a snapshot carrying a module no accepted - /// observation of that batch resolves is rejected at publication, so an - /// unrelated module can never enter the published lineage (and therefore can - /// never reach a successor stage/close, which read only the published view). - #[test] - fn observation_batch_rejects_an_injected_module() { - let error = tampered_batch_error(|ctx| { - let mut assembler = crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - Arc::new("fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }".to_owned()), - ) - .unwrap(); - // An extra module with provenance but NO justifying observation. - add_trusted_option(&mut assembler); - assembler + fn refresh_retention_metrics(&mut self) { + let diagnostics = self.diagnostics.retention_metrics(); + + let stores = [self.queries.revisioned.parse_retention()]; + + let mut pinned_attempts = BTreeSet::new(); + pinned_attempts.extend(self.queries.revisioned.parse_origin_attempt_ids()); + self.metrics.set_pinned_origins(pinned_attempts); + + self.metrics.set_retention(FrontendRetentionMetrics { + retained_query_records: stores.iter().map(|store| store.retained).sum(), + protected_query_records: stores.iter().map(|store| store.protected).sum(), + dependency_pins: stores.iter().map(|store| store.pinned).sum(), + validation_tombstones: stores.iter().map(|store| store.tombstones).sum(), + graph_retained_disappeared_nodes: 0, + query_evictions: stores.iter().map(|store| store.evictions).sum(), + aborted_query_attempts: self.queries.revisioned.parse_retained_aborted_len(), + import_query_entries: 0, + import_query_evictions: 0, + semantic_query_entries: 0, + semantic_query_evictions: 0, + definition_query_entries: 0, + definition_query_evictions: 0, + diagnostic_entries: diagnostics.entries, + diagnostic_source_attempts: diagnostics.source_attempts, + diagnostic_source_bytes: diagnostics.source_bytes, }); - assert!( - error.contains("must equal this step's authorized additions exactly"), - "{error}", - ); } - /// A batch cannot MUTATE a predecessor module under its ID: a snapshot whose - /// root module has the same identity but different content is rejected at - /// publication (the lineage is strictly additive at that boundary). - #[test] - fn observation_batch_rejects_a_mutated_predecessor_source() { - let error = tampered_batch_error(|ctx| { - crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - // Same root identity, DIFFERENT body. - Arc::new("fn main() -> i32 { let _ = @parse_i64(\"1\"); 42 }".to_owned()), - ) - .unwrap() - }); - assert!( - error.contains("mutates a predecessor module source"), - "{error}", - ); + pub fn update(&mut self, snapshot: &SourceSnapshot) -> CompilerSessionUpdate { + // A source update supersedes the predecessor any outstanding + // trusted-toolchain continuation or successor-delta authority was + // issued against (RUE-1112): a stale capability can neither stage nor + // close over an artifact the update replaced. + self.continuation = None; + self.successor_delta_nonce = None; + self.select_diagnostic_presentation(None); + let provenance = self.syntax_diagnostic_provenance(); + self.run_parse_update(snapshot, provenance) } - /// A batch cannot OMIT an accepted module: publishing the exact - /// compiler-issued accepted observation for a newly resolved module while - /// omitting that module from the successor snapshot is rejected — the - /// additions must EQUAL the batch's accepted resolutions in both - /// directions, so topology can never claim "resolved" without the module's - /// source leaf behind it. - #[test] - fn observation_batch_rejects_omitting_an_accepted_module() { - let ctx = continuation_std_context(); - let root_source = "const a = @import(\"a.rue\"); fn main() -> i32 { 0 }"; - let mut assembler = crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - Arc::new(root_source.to_owned()), - ) - .unwrap(); - let snapshot = assembler.snapshot().unwrap(); - let reads = assembler.accepted_read_manifest(); - let mut session = CompilerSession::new(); - let revision = session - .begin_import_input_request(&snapshot, ctx.clone(), reads.clone()) - .unwrap(); - let plan = session - .stage_import_discovery( - &snapshot, - ctx.clone(), - reads.shared_slice(), - crate::ImportObservationLedger::default(), - ) - .unwrap(); - let roots = plan.demand_roots(); - let frontier = session - .import_demand_frontier_for_roots( - revision, - &plan, - crate::ImportDemandMode::Rooted, - &roots, - ) - .unwrap(); - assert!( - !frontier.requests().is_empty(), - "an unresolved import demands host reads", - ); + /// Publish a snapshot while retaining its caller-selected presentation order. + /// + /// Query artifacts still use stable module identity. Only syntax and merge + /// diagnostic ordering follows [`SourceSnapshot::files`], which is useful + /// for command-line and other presentation-oriented consumers. + pub(crate) fn update_for_presentation( + &mut self, + snapshot: &SourceSnapshot, + ) -> CompilerSessionUpdate { + // A presentation update replaces the retained parse artifact exactly + // like a source update, so it likewise supersedes any outstanding + // trusted-toolchain continuation or successor-delta authority + // (RUE-1112). + self.continuation = None; + self.successor_delta_nonce = None; + self.select_diagnostic_presentation(Some(crate::shared_segments::SharedList::flat( + snapshot + .files() + .map(|source| snapshot.module_id(source.file_id).unwrap().clone()) + .collect(), + ))); + let provenance = self.syntax_diagnostic_provenance(); + self.run_parse_update(snapshot, provenance) + } - // Answer the frontier honestly for a.rue: the exact compiler-issued - // accepted observation, absent elsewhere. - let module_source = "pub fn value() -> i32 { 1 }"; - let observations: Vec = frontier - .requests() - .iter() - .map(|request| { - if request.requested_path() == "/project/a.rue" { - crate::ImportObservation::accepted( - request.clone(), - crate::AcceptedImportSource::new( - Arc::from("/project/a.rue"), - Arc::from("/project/a.rue"), - crate::PhysicalFileIdentity::new(5, 5), - continuation_metadata(), - Arc::new(module_source.to_owned()), - ) - .unwrap(), - ) - .unwrap() - } else { - crate::ImportObservation::absent(request.clone()) + fn adopt_discovery_program_for_presentation( + &mut self, + snapshot: &SourceSnapshot, + _program: Arc, + _work: ParsedModulesWork, + successor: bool, + retained_successor_parse: Option, + ) -> CompilerSessionUpdate { + // A trusted-successor close adopts by RE-SELECTING the exact successor + // parse terminal its stage computed and retained on the open artifact + // — same key, same revision — never by re-deriving an extension + // against the now-selected successor state (which would mint a second + // empty-extension terminal). A missing retained terminal rejects the + // close. + if successor { + return match retained_successor_parse { + Some(record) => self.run_parse_update_successor(snapshot, record), + None => { + let errors = CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "trusted-toolchain successor close rejected: the staged successor parse terminal is not retained".into(), + ), + )); + let diagnostics = Arc::new(FrontendDiagnosticSnapshot { + source: snapshot.clone(), + stage: FrontendDiagnosticIdentity::Syntax, + provenance: DiagnosticAttemptProvenance::Canonical, + errors: errors.as_slice().to_vec().into(), + warnings: Arc::from([]), + }); + CompilerSessionUpdate { + result: Err(errors), + work: ParsedModulesWork::default(), + #[cfg(test)] + invalidation: ParseInvalidationSummary::default(), + downstream_invalidated: false, + diagnostics, + } } + }; + } + self.select_diagnostic_presentation(Some(crate::shared_segments::SharedList::flat( + snapshot + .files() + .map(|source| snapshot.module_id(source.file_id).unwrap().clone()) + .collect(), + ))); + let provenance = self.syntax_diagnostic_provenance(); + self.run_parse_update(snapshot, provenance) + } + + fn parse_baseline(&self) -> Option> { + self.queries + .revisioned + .last_good_parse_record() + .and_then(|record| record.result.as_ref().ok()) + .cloned() + } + + fn parse_invalidation(&self, snapshot: &SourceSnapshot) -> ParseInvalidationSummary { + let baseline = self.parse_baseline(); + classify_invalidation(snapshot, baseline.as_deref()) + } + + fn syntax_diagnostic_provenance(&self) -> DiagnosticAttemptProvenance { + self.batch_diagnostic_order + .as_ref() + .map_or(DiagnosticAttemptProvenance::Canonical, |order| { + DiagnosticAttemptProvenance::Presentation(order.clone()) }) - .collect(); + } - // A manifest carrying the resolved module's provenance, but a snapshot - // OMITTING the module itself. - let mut with_module = crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - crate::PhysicalFileIdentity::new(1, 1), - continuation_metadata(), - Arc::new(root_source.to_owned()), - ) - .unwrap(); - with_module - .add_explicit( - "/project/a.rue", - "/project/a.rue", - crate::PhysicalFileIdentity::new(5, 5), - continuation_metadata(), - Arc::new(module_source.to_owned()), + fn select_diagnostic_presentation( + &mut self, + order: Option>, + ) { + self.batch_diagnostic_order = order; + } + + fn execute_parse_query( + &mut self, + snapshot: &SourceSnapshot, + presentation: DiagnosticAttemptProvenance, + attempt_id: AttemptId, + ) -> ( + ParseQueryRecord, + Arc, + QueryAttemptExecution, + ParsedModulesWork, + ParseInvalidationSummary, + ) { + // Keying, module parsing, and terminal publication are separate costs + // inside the parse query. Timing them apart keeps the staging residual + // from hiding whole-snapshot content hashing behind `parse_file` + // (RUE-786). + let key_span = tracing::info_span!("parse_query_key").entered(); + let source = ExactSourceInput::new(snapshot); + // An ordinary key carries every file's exact content identity, so the + // typed store hashes and compares each of them. + self.parse_key_entries_compared = self + .parse_key_entries_compared + .saturating_add(snapshot.len() as u64); + let key = ParseQueryKey::Ordinary(Box::new(OrdinaryParseKey { + source: source.clone(), + file_order: snapshot + .files() + .map(|source| source.file_id) + .collect::>() + .into(), + presentation: presentation.clone(), + })); + let revision = self.queries.revisioned.source_revision(&source, snapshot); + let demanded_modules = match &presentation { + DiagnosticAttemptProvenance::Canonical => snapshot + .source_revision() + .modules() + .iter() + .map(|source| source.module.clone()) + .collect::>(), + DiagnosticAttemptProvenance::Presentation(order) => order.iter().cloned().collect(), + }; + self.parse_sources_materialized = self + .parse_sources_materialized + .saturating_add(demanded_modules.len() as u64); + self.parse_modules_dispatched = self + .parse_modules_dispatched + .saturating_add(demanded_modules.len() as u64); + drop(key_span); + let (modular_result, modular_work) = { + let _span = tracing::info_span!("parse_program").entered(); + self.queries.revisioned.parse_program( + revision, + snapshot.source_revision().root(), + demanded_modules, ) - .unwrap(); - let reads_with_module = with_module.accepted_read_manifest(); - let err = session - .publish_import_observation_batch(&frontier, &snapshot, reads_with_module, observations) - .unwrap_err() - .to_string(); - assert!( - err.contains("must equal this step's authorized additions exactly"), - "{err}", + }; + let _commit_span = tracing::info_span!("parse_query_commit").entered(); + self.parse_invalidation_entries_compared = self + .parse_invalidation_entries_compared + .saturating_add(snapshot.len() as u64); + let baseline = self.parse_baseline(); + let attempt = + self.queries + .revisioned + .request_parse(revision, attempt_id, key.clone(), |context| { + context.input(rue_query::InputIdentity::new( + crate::revisioned_query_database::RevisionedQueryDatabase::SOURCE_INPUT, + "current", + ))?; + let work = modular_work; + let invalidation = classify_invalidation(snapshot, baseline.as_deref()); + let result = modular_result; + // Freeze diagnostics privately with the query output. Session + // selection and metrics happen only after atomic publication. + let diagnostics = Arc::new(FrontendDiagnosticSnapshot { + source: snapshot.clone(), + stage: FrontendDiagnosticIdentity::Syntax, + provenance: presentation.clone(), + errors: result.as_ref().err().map_or_else( + || Arc::from([]), + |errors| errors.as_slice().to_vec().into(), + ), + warnings: Arc::from([]), + }); + Ok(ParseQueryRecord { + key, + runtime_revision: revision, + snapshot: snapshot.clone(), + result, + diagnostics, + work, + invalidation, + }) + }); + self.queries.revisioned.select_parse(&attempt); + let terminal = attempt + .terminal() + .unwrap_or_else(|| panic!("parse query aborted: {:?}", attempt.abort())); + let record = match terminal.outcome() { + rue_query::QueryOutcome::Success(record) => record.clone(), + rue_query::QueryOutcome::Failure(_) => unreachable!("parse retains typed records"), + }; + let execution = match attempt.execution() { + rue_query::RequestExecution::Computed => { + self.metrics + .diagnostic_publication(self.diagnostics.latest().is_some()); + QueryAttemptExecution::Computed + } + rue_query::RequestExecution::Reused | rue_query::RequestExecution::Joined => { + self.reuse_diagnostics(record.diagnostics.clone()); + QueryAttemptExecution::Reused + } + rue_query::RequestExecution::Aborted => unreachable!(), + }; + let work = if execution == QueryAttemptExecution::Computed { + record.work + } else { + ParsedModulesWork::default() + }; + let invalidation = if execution == QueryAttemptExecution::Computed { + record.invalidation.clone() + } else { + self.parse_invalidation(snapshot) + }; + let view = self.queries.revisioned.parse_attempt_view( + attempt_id, + attempt, + QueryStructuralWork::Parse(work), ); + self.diagnostics.select(view.clone()); + (record, view, execution, work, invalidation) + } + + /// Reconcile one successor parse extension without side effects: the + /// retained parse artifact this stage extends (within one trusted re-close, + /// the committed predecessor for the first stage and the prior successor + /// stage after a frontier batch), its presentation order, and the appended + /// (module, file) pairs. The retained artifact must PROVE it is the + /// successor snapshot's structural ancestor — every one of its source + /// segments carried by `Arc` identity, same root, and its exact + /// presentation order — so a parse record from any other snapshot (an + /// intervening source or presentation update) can never be extended; the + /// capability is rejected instead. Everything here is O(appended); content + /// identity is pinned by the published revision, never re-hashed or + /// re-compared. + fn prepare_successor_parse( + &self, + snapshot: &SourceSnapshot, + delta: &Arc<[crate::ModuleRevision]>, + ) -> Result { + let reject = |message: &str| { + CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( + format!("trusted-toolchain successor parse rejected: {message}"), + ))) + }; + let Some(terminal) = self.queries.revisioned.last_good_parse_terminal() else { + return Err(reject("no predecessor parse artifact is retained")); + }; + let Ok(predecessor_terminal) = self + .queries + .revisioned + .parse_family() + .adoptable_terminal(terminal) + else { + return Err(reject( + "the retained predecessor parse terminal is not adoptable", + )); + }; + let rue_query::QueryOutcome::Success(record) = predecessor_terminal.terminal().outcome() + else { + return Err(reject("the retained predecessor parse artifact failed")); + }; + let Ok(predecessor_program) = record.result.as_ref().cloned() else { + return Err(reject("the retained predecessor parse artifact failed")); + }; + let DiagnosticAttemptProvenance::Presentation(predecessor_order) = + &record.diagnostics.provenance + else { + return Err(reject( + "the retained parse artifact carries no staging presentation order", + )); + }; + let predecessor_order = predecessor_order.clone(); + let predecessor_revision = record.runtime_revision; + // STRUCTURAL ANCESTRY: the successor snapshot must carry every source + // segment of the retained artifact's snapshot by `Arc` identity, with + // the same root. An artifact retained by an intervening update over a + // different or reordered snapshot cannot share this lineage and is + // rejected here rather than silently extended. + let predecessor_snapshot = record.snapshot.clone(); + { + let successor_segments = snapshot.source_revision().module_segments().segments(); + let predecessor_segments = predecessor_snapshot + .source_revision() + .module_segments() + .segments(); + let shared_prefix = successor_segments.len() >= predecessor_segments.len() + && successor_segments + .iter() + .zip(predecessor_segments.iter()) + .all(|(a, b)| Arc::ptr_eq(a, b)); + if !shared_prefix + || snapshot.source_revision().root() + != predecessor_snapshot.source_revision().root() + { + return Err(reject( + "the retained parse artifact is not the successor snapshot's structural ancestor", + )); + } + } + let predecessor_len = predecessor_program.modules_len(); + if predecessor_len != predecessor_snapshot.len() + || predecessor_order.len() != predecessor_len + { + return Err(reject( + "the retained parse artifact does not cover its own snapshot", + )); + } + // A re-stage whose snapshot appended nothing since the retained parse + // (a frontier round that only grew observations) extends with an empty + // delta and reuses every retained module. + if predecessor_len > snapshot.len() || snapshot.len() - predecessor_len > delta.len() { + return Err(reject( + "the successor snapshot does not extend the retained parse artifact by the authorized delta", + )); + } + // The appended sources extend the predecessor's dense file table, so + // the appended (module, file) pairs are exactly the tail file IDs. + let mut appended = Vec::with_capacity(snapshot.len() - predecessor_len); + for index in predecessor_len as u32 + 1..=snapshot.len() as u32 { + let file_id = crate::FileId::new(index); + let Some(module) = snapshot.module_id(file_id) else { + return Err(reject("an appended source has no logical module")); + }; + appended.push((module.clone(), file_id)); + } + // Every appended module revision must be one of the + // capability-verified additions. The capability delta is cumulative + // since the committed close; the parse key below keeps only this + // stage's exact suffix. + let mut segment = Vec::with_capacity(appended.len()); + for (module, file_id) in &appended { + let source = snapshot + .source_id(*file_id) + .expect("the appended source has a stable content identity") + .clone(); + let Ok(index) = delta.binary_search_by(|revision| revision.module.cmp(module)) else { + return Err(reject( + "an appended module is outside the capability-verified delta", + )); + }; + if delta[index].source != source { + return Err(reject( + "an appended module's source differs from the capability-verified delta", + )); + } + segment.push(crate::ModuleRevision { + module: module.clone(), + source, + }); + } + segment.sort_by(|left, right| left.module.cmp(&right.module)); + Ok(PreparedSuccessorParse { + predecessor_program, + predecessor_order, + predecessor_revision, + predecessor_terminal, + appended, + segment: segment.into(), + }) } - /// A batch cannot SUBSTITUTE predecessor provenance: an accepted-read entry - /// for an existing module with altered physical identity is rejected at - /// publication. - #[test] - fn observation_batch_rejects_substituted_provenance() { - let error = tampered_batch_error(|ctx| { - crate::DiscoverySourceAssembler::new( - ctx.clone(), - "/project/main.rue", - "/project/main.rue", - // Same content, DIFFERENT physical identity: the module revision - // matches but its provenance record does not. - crate::PhysicalFileIdentity::new(7, 7), - continuation_metadata(), - Arc::new("fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }".to_owned()), - ) - .unwrap() - }); - assert!( - error.contains("mutates a predecessor accepted-read provenance"), - "{error}", + /// The successor parse projection (RUE-1112): keyed on the published + /// lineage identity plus the exact appended segment, parsing ONLY the + /// appended modules and structurally extending the retained predecessor + /// parsed program and presentation order. + #[allow(clippy::type_complexity)] + fn execute_parse_query_successor( + &mut self, + snapshot: &SourceSnapshot, + revision: crate::ImportInputRevision, + prepared: PreparedSuccessorParse, + attempt_id: AttemptId, + ) -> ( + ParseQueryRecord, + Arc, + QueryAttemptExecution, + ParsedModulesWork, + ParseInvalidationSummary, + ) { + let PreparedSuccessorParse { + predecessor_program, + predecessor_order, + predecessor_revision, + predecessor_terminal, + appended, + segment, + } = prepared; + let successor_order = crate::shared_segments::SharedList::extend( + &predecessor_order, + appended.iter().map(|(module, _)| module.clone()).collect(), ); - } + self.select_diagnostic_presentation(Some(successor_order.clone())); + let presentation = DiagnosticAttemptProvenance::Presentation(successor_order); - #[test] - fn stable_no_filesystem_boundary_classifies_unsatisfied_toolchain_input_not_ice() { - // RUE-1112 C3: the stable no-filesystem `canonical_semantic` entry cannot - // acquire, so an unsatisfied trusted-toolchain demand for otherwise-valid - // source is a deterministic CONTRACT failure, never an ICE (E9000). - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }", - )], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - let error = errors.first().expect("unsatisfied toolchain input error"); - assert!( - matches!( - error.kind, - rue_error::ErrorKind::UnsatisfiedTrustedToolchainInput(_) - ), - "expected an unsatisfied-trusted-toolchain-input classification, got {:?}", - error.kind - ); - assert_ne!(error.kind.code(), rue_error::ErrorCode::INTERNAL_ERROR); - assert_eq!( - error.kind.code(), - rue_error::ErrorCode::UNSATISFIED_TRUSTED_TOOLCHAIN_INPUT + // A successor key embeds only the published lineage identity and its + // appended segment. + self.parse_key_entries_compared = self + .parse_key_entries_compared + .saturating_add(segment.len() as u64); + let key = ParseQueryKey::Successor { + revision, + segment, + predecessor: predecessor_revision, + }; + let runtime_revision = + // The runtime revision's compatibility slot is the observation + // regime, not the per-request counter (RUE-1137). This must match + // how import publication built the revision, or the module-input + // and parse projections cannot find their published views. + rue_query::Revision::new(revision.revision_id, revision.compatibility_token); + self.parse_modules_dispatched = self + .parse_modules_dispatched + .saturating_add(appended.len() as u64); + let (modular_result, modular_work) = self.queries.revisioned.parse_program_extension( + runtime_revision, + &predecessor_program, + &appended, ); - } - - #[test] - fn provider_observation_counters_record_exact_production_work() { - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn helper(x: i32) -> i32 { x + 1 } fn main() -> i32 { helper(2) }", - )], - 1, + self.parse_invalidation_entries_compared = self + .parse_invalidation_entries_compared + .saturating_add(appended.len() as u64); + let appended_modules: Vec = + appended.iter().map(|(module, _)| module.clone()).collect(); + let parse_family = self.queries.revisioned.parse_family(); + let attempt = self.queries.revisioned.request_parse( + runtime_revision, + attempt_id, + key.clone(), + |context| { + // The record adopts the CAPTURED predecessor parse terminal as a + // runtime dependency — the exact terminal held by preparation, + // observed by node, incarnation, and stamp with no key hash or + // content comparison — so successor-after-predecessor is a real + // query edge: red/green validation and leases flow through it, + // and the node's endorsement at this revision carries the exact + // stamp to every compatible descendant. Adoption is sound here + // because parse keys are content-addressed: the key alone pins + // the terminal's value. A stale or evicted terminal aborts the + // attempt rather than being silently re-derived. + if parse_family + .observe_adopted_terminal(context, &predecessor_terminal) + .is_err() + { + return Err(rue_query::QueryAbort::Canceled); + } + // Plus exactly the appended modules' input leaves; the remaining + // predecessor content is pinned by the dependency above and the + // published lineage identity in the key. + for (module, _) in &appended { + context.input( + crate::revisioned_query_database::RevisionedQueryDatabase::module_source_input( + module, + ), + )?; + } + let work = modular_work; + let invalidation = + crate::parsed_modules::classify_successor_invalidation(&appended_modules); + let result = modular_result; + // Freeze diagnostics privately with the query output. Session + // selection and metrics happen only after atomic publication. + let diagnostics = Arc::new(FrontendDiagnosticSnapshot { + source: snapshot.clone(), + stage: FrontendDiagnosticIdentity::Syntax, + provenance: presentation.clone(), + errors: result + .as_ref() + .err() + .map_or_else(|| Arc::from([]), |errors| errors.as_slice().to_vec().into()), + warnings: Arc::from([]), + }); + Ok(ParseQueryRecord { + key, + runtime_revision, + snapshot: snapshot.clone(), + result, + diagnostics, + work, + invalidation, + }) + }, ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .expect("the program analyzes"); - let metrics = crate::unstable::provider_observation_metrics(&session); - assert_eq!(metrics.name_lookups, 3); - assert_eq!(metrics.import_lookups, 0); - assert_eq!(metrics.method_candidates, 0); - assert_eq!(metrics.operator_candidates, 0); - assert_eq!(metrics.declaration_facts, 25); - assert!(metrics.identity_facts > 0, "{metrics:?}"); - assert!(metrics.signature_facts > 0, "{metrics:?}"); - assert!(metrics.materializations > 0, "{metrics:?}"); - assert_eq!( - metrics.declaration_facts, - metrics.identity_facts - + metrics.signature_facts - + metrics.type_facts - + metrics.const_facts, - "the declaration aggregate must be exactly partitioned by real fact families" + self.queries.revisioned.select_parse(&attempt); + let terminal = attempt + .terminal() + .unwrap_or_else(|| panic!("parse query aborted: {:?}", attempt.abort())); + let record = match terminal.outcome() { + rue_query::QueryOutcome::Success(record) => record.clone(), + rue_query::QueryOutcome::Failure(_) => unreachable!("parse retains typed records"), + }; + let execution = match attempt.execution() { + rue_query::RequestExecution::Computed => { + self.metrics + .diagnostic_publication(self.diagnostics.latest().is_some()); + QueryAttemptExecution::Computed + } + rue_query::RequestExecution::Reused | rue_query::RequestExecution::Joined => { + self.reuse_diagnostics(record.diagnostics.clone()); + QueryAttemptExecution::Reused + } + rue_query::RequestExecution::Aborted => unreachable!(), + }; + let work = if execution == QueryAttemptExecution::Computed { + record.work + } else { + ParsedModulesWork::default() + }; + // A successor record's classification is relative to the retained + // predecessor its key pins, so the reused branch reuses it verbatim. + let invalidation = record.invalidation.clone(); + let view = self.queries.revisioned.parse_attempt_view( + attempt_id, + attempt, + QueryStructuralWork::Parse(work), ); - assert_eq!(metrics.anonymous_facts, 0); - assert_eq!(metrics.producer_facts, 0); - assert_eq!(metrics.toolchain_facts, 4); + self.diagnostics.select(view.clone()); + (record, view, execution, work, invalidation) } - #[test] - fn published_lookup_root_lease_retains_production_body_lookups() { - // Production body analysis publishes its exact lookup-name terminals - // into the session lease. The lease owns retention independently from - // the request-scoped provider that observed those terminals. - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn helper(x: i32) -> i32 { x + 1 } fn main() -> i32 { helper(2) }", - )], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .expect("the program analyzes"); + fn parse_staging_snapshot( + &mut self, + snapshot: &SourceSnapshot, + successor: Option<(crate::ImportInputRevision, &Arc<[crate::ModuleRevision]>)>, + ) -> ( + Result, CompileErrors>, + ParsedModulesWork, + Option, + ) { + // A successor stage MUST extend its verified predecessor: a failed + // predecessor binding rejects the stage rather than silently falling + // back to a full content-keyed build under successor authority. + let prepared_successor = match successor { + Some((revision, delta)) => match self.prepare_successor_parse(snapshot, delta) { + Ok(prepared) => Some((revision, prepared)), + Err(errors) => return (Err(errors), ParsedModulesWork::default(), None), + }, + None => None, + }; + let staged_successor = prepared_successor.is_some(); + let mut guard = self.metrics.begin_unprojected("parse"); + let attempt_id = guard.id; + let (record, view, execution, work, _invalidation) = match prepared_successor { + Some((revision, prepared)) => { + self.execute_parse_query_successor(snapshot, revision, prepared, attempt_id) + } + None => { + let order = snapshot + .files() + .map(|source| snapshot.module_id(source.file_id).unwrap().clone()) + .collect::>(); + self.parse_sources_materialized = self + .parse_sources_materialized + .saturating_add(order.len() as u64); + let order = crate::shared_segments::SharedList::flat(order.into()); + self.select_diagnostic_presentation(Some(order.clone())); + let presentation = DiagnosticAttemptProvenance::Presentation(order); + self.execute_parse_query(snapshot, presentation, attempt_id) + } + }; + guard.started(); + let result = record.result.clone(); + guard.attach_diagnostics(record.diagnostics.clone()); + guard.bind(view); + guard.finish(execution, None, &result, QueryStructuralWork::None); + self.metrics.synchronize(); + let retained = staged_successor.then(|| record.clone()); + (result, work, retained) + } - let metrics = crate::unstable::lookup_pressure_metrics(&session); - assert!(metrics.published_roots > 0, "{metrics:?}"); - assert!(metrics.leased_terminals > 0, "{metrics:?}"); - assert!(metrics.retained_logical_keys > 0, "{metrics:?}"); - assert!(metrics.retained_family_nodes > 0, "{metrics:?}"); - assert!(metrics.retained_family_terminals > 0, "{metrics:?}"); - assert_eq!( - metrics.protected_growth, 0, - "no lease supersession grew a family" - ); - assert_eq!( - metrics.evictions, 0, - "no lease supersession evicted a terminal" - ); - assert_eq!(metrics.rederivations_after_eviction, 0); - // Exact lookup collection runs through the same provider-backed body - // transaction as production analysis. - assert!( - crate::unstable::provider_observation_metrics(&session).name_lookups > 0, - "production lookup terminals must be observed by the provider" + fn run_parse_update( + &mut self, + snapshot: &SourceSnapshot, + presentation: DiagnosticAttemptProvenance, + ) -> CompilerSessionUpdate { + let mut guard = self.metrics.begin_unprojected("parse"); + let attempt_id = guard.id; + let (record, view, execution, parse_work, invalidation) = + self.execute_parse_query(snapshot, presentation, attempt_id); + guard.started(); + self.metrics.update(parse_work, invalidation.clone()); + let result = record.result.clone(); + let diagnostics = record.diagnostics.clone(); + guard.attach_diagnostics(diagnostics.clone()); + guard.bind(view); + guard.finish(execution, None, &result, QueryStructuralWork::None); + self.metrics.synchronize(); + match result { + Ok(candidate) => { + if self.open_discovery.as_deref().is_some_and(|artifact| { + artifact.source_revision != *candidate.source_revision() + }) { + self.open_discovery = None; + } + let exact = self.published.as_deref().is_some_and(|published| { + programs_are_pointer_equivalent(published, &candidate) + }); + let downstream_invalidated = self.published.is_some() && !exact; + if exact { + self.published_snapshot = Some(snapshot.clone()); + CompilerSessionUpdate { + result: Ok(self.published.as_ref().unwrap().clone()), + work: parse_work, + #[cfg(test)] + invalidation, + downstream_invalidated: false, + diagnostics, + } + } else { + self.metrics + .project_dependency_invalidations(downstream_invalidated); + self.published = Some(candidate.clone()); + self.published_snapshot = Some(snapshot.clone()); + self.refresh_retention_metrics(); + CompilerSessionUpdate { + result: Ok(candidate), + work: parse_work, + #[cfg(test)] + invalidation, + downstream_invalidated, + diagnostics, + } + } + } + Err(errors) => CompilerSessionUpdate { + result: Err(errors), + work: parse_work, + #[cfg(test)] + invalidation, + downstream_invalidated: false, + diagnostics, + }, + } + } + + /// The successor-close counterpart of [`Self::run_parse_update`]: adopts + /// the successor parse terminal for semantic queries with the same + /// publication bookkeeping, without re-running the whole-program + /// content-keyed projection (RUE-1112). The candidate extends the retained + /// predecessor by construction, so downstream invalidation follows from an + /// existing publication rather than a module-table comparison. + fn run_parse_update_successor( + &mut self, + snapshot: &SourceSnapshot, + retained: ParseQueryRecord, + ) -> CompilerSessionUpdate { + let mut guard = self.metrics.begin_unprojected("parse"); + let attempt_id = guard.id; + // Re-request the exact staged terminal: same key, same revision. The + // stage's selection protects that terminal, so this reuses it without + // publishing anything new; the recompute body republishes the retained + // record verbatim only if the terminal were ever evicted. + if let ParseQueryKey::Successor { segment, .. } = &retained.key { + self.parse_key_entries_compared = self + .parse_key_entries_compared + .saturating_add(segment.len() as u64); + } + let key = retained.key.clone(); + let runtime_revision = retained.runtime_revision; + let recompute = retained.clone(); + let attempt = + self.queries + .revisioned + .request_parse(runtime_revision, attempt_id, key, |context| { + for module in &recompute.invalidation.added { + context.input( + crate::revisioned_query_database::RevisionedQueryDatabase::module_source_input( + module, + ), + )?; + } + Ok(recompute.clone()) + }); + self.queries.revisioned.select_parse(&attempt); + let terminal = attempt + .terminal() + .unwrap_or_else(|| panic!("parse query aborted: {:?}", attempt.abort())); + let record = match terminal.outcome() { + rue_query::QueryOutcome::Success(record) => record.clone(), + rue_query::QueryOutcome::Failure(_) => unreachable!("parse retains typed records"), + }; + let execution = match attempt.execution() { + rue_query::RequestExecution::Computed => QueryAttemptExecution::Computed, + rue_query::RequestExecution::Reused | rue_query::RequestExecution::Joined => { + self.reuse_diagnostics(record.diagnostics.clone()); + QueryAttemptExecution::Reused + } + rue_query::RequestExecution::Aborted => unreachable!(), + }; + // The stage already accounted this terminal's parse work; re-selecting + // it at close performs none. + let parse_work = ParsedModulesWork::default(); + let invalidation = record.invalidation.clone(); + let view = self.queries.revisioned.parse_attempt_view( + attempt_id, + attempt, + QueryStructuralWork::Parse(parse_work), ); + self.diagnostics.select(view.clone()); + guard.started(); + self.metrics.update(parse_work, invalidation.clone()); + let result = record.result.clone(); + let diagnostics = record.diagnostics.clone(); + guard.attach_diagnostics(diagnostics.clone()); + guard.bind(view); + guard.finish(execution, None, &result, QueryStructuralWork::None); + self.metrics.synchronize(); + match result { + Ok(candidate) => { + if self.open_discovery.as_deref().is_some_and(|artifact| { + artifact.source_revision != *candidate.source_revision() + }) { + self.open_discovery = None; + } + let downstream_invalidated = self.published.is_some(); + // The predecessor source leaf stays live: additive adoption + // must not disappear it and transitively invalidate every + // retained terminal that still correctly depends on it. + self.metrics.project_dependency_invalidations(false); + self.published = Some(candidate.clone()); + self.published_snapshot = Some(snapshot.clone()); + self.refresh_retention_metrics(); + CompilerSessionUpdate { + result: Ok(candidate), + work: parse_work, + #[cfg(test)] + invalidation, + downstream_invalidated, + diagnostics, + } + } + Err(errors) => CompilerSessionUpdate { + result: Err(errors), + work: parse_work, + #[cfg(test)] + invalidation, + downstream_invalidated: false, + diagnostics, + }, + } } - #[test] - fn successful_closure_retires_unreachable_and_deleted_body_lookup_roots() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn helper() -> i32 { 1 } fn main() -> i32 { helper() }", - )], - 1, - ); - let unreachable = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn helper() -> i32 { 1 } fn main() -> i32 { 0 }", - )], - 1, - ); - let deleted = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .expect("initial closure analyzes"); - let initial = crate::unstable::lookup_pressure_metrics(&session); - assert_eq!(initial.published_roots, 2, "{initial:?}"); + /// Return the graph adopted by import discovery. + /// + /// Import-free direct sessions synthesize the uniquely valid empty graph; + /// import-bearing sessions never reconstruct resolution from loaded paths. + pub fn import_graph( + &mut self, + std_dir: Option<&str>, + ) -> Result, CompileErrors> { + let mut guard = self.metrics.begin::(); + let mut execution = QueryAttemptExecution::Rejected; + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.import_graph_attempt(std_dir, &mut guard, &mut execution) + })); + let result = match result { + Ok(result) => result, + Err(payload) => self.resume_canceled_query(&mut guard, payload), + }; + if execution == QueryAttemptExecution::Reused { + execution = QueryAttemptExecution::Adopted; + } + guard.finish(execution, None, &result, QueryStructuralWork::None); + self.metrics.synchronize(); + result + } - session.update(&unreachable).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .expect("unreachable successor analyzes"); - let after_unreachable = crate::unstable::lookup_pressure_metrics(&session); - assert_eq!( - after_unreachable.published_roots, 1, - "helper root must retire when it leaves the reached closure: {after_unreachable:?}" - ); + fn import_graph_attempt( + &mut self, + std_dir: Option<&str>, + guard: &mut QueryComputationGuard, + execution: &mut QueryAttemptExecution, + ) -> Result, CompileErrors> { + self.require_closed_discovery()?; + if let Some(committed) = self.committed_import_discovery_artifact() { + let graph = committed + .graph() + .expect("closed-valid discovery revisions retain their canonical graph"); + if graph.input().std_dir.as_deref() != std_dir { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "the requested standard-library context differs from the committed import discovery revision" + .into(), + ), + ))); + } + *execution = QueryAttemptExecution::Reused; + return Ok(graph.clone()); + } + let parsed = self.published.clone().ok_or_else(no_published_program)?; + if !parsed.import_directives().is_empty() { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "import-bearing revisions require a committed discovery graph".into(), + ), + ))); + } + let resolution = ModuleResolutionInputs::new( + parsed.root().clone(), + parsed + .modules() + .iter() + .map(|module| crate::ModuleResolutionInput { + module: module.module_id().clone(), + physical_path: Arc::from(module.physical_path()), + }) + .collect(), + ) + .expect("published parsed modules have validated resolution inputs"); + let input = ImportGraphInputDescriptor { + sources: parsed.source_revision().clone(), + resolution, + std_dir: std_dir.map(Arc::from), + }; + *execution = QueryAttemptExecution::Computed; + guard.started(); + let graph = crate::import_graph::import_free_canonical_graph(parsed.as_ref())?; + let validation = validate_canonical_import_graph(&graph, &input.resolution); + Ok(Arc::new(CanonicalImportGraphOutput { + input, + graph, + validation, + })) + } - session.update(&deleted).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .expect("deleted successor analyzes"); - let after_deleted = crate::unstable::lookup_pressure_metrics(&session); - assert_eq!( - after_deleted.published_roots, 1, - "deleting an already-unreachable body cannot resurrect its root: {after_deleted:?}" - ); + pub(crate) fn merge(&mut self) -> Result, CompileErrors> { + let mut guard = self.metrics.begin::(); + let attempt_id = guard.id; + let mut execution = QueryAttemptExecution::Rejected; + let mut origin = None; + let mut attempt_work = None; + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.merge_attempt( + attempt_id, + &mut guard, + &mut execution, + &mut origin, + &mut attempt_work, + ) + })); + let result = match result { + Ok(result) => result, + Err(payload) => self.resume_canceled_query(&mut guard, payload), + }; + let structural = attempt_work + .map(QueryStructuralWork::Merge) + .unwrap_or(QueryStructuralWork::None); + guard.finish(execution, origin, &result, structural); + self.metrics.synchronize(); + result } - #[test] - fn nested_duplicate_parameter_diagnostics_rejoin_the_exact_current_occurrence() { - for source_text in [ - "struct S {\n fn m(self, a: i32, a: i32) {}\n}\nfn main() {}", - "struct S {\n fn make(a: i32, a: i32) {}\n}\nfn main() {}", - ] { - let duplicate_start = source_text - .rfind("a: i32") - .expect("fixture contains the duplicate parameter"); - let expected = rue_span::Span::with_file( - FileId::new(1), - duplicate_start as u32, - (duplicate_start + "a: i32".len()) as u32, - ); - let source = snapshot(&[(1, "/p/main.rue", "main.rue", source_text)], 1); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - let error = errors.first().expect("duplicate parameter diagnostic"); - assert!( - error.to_string().contains("duplicate parameter name 'a'"), - "unexpected diagnostic: {error}" - ); - assert_eq!(error.span(), Some(expected)); + fn merge_attempt( + &mut self, + _attempt_id: AttemptId, + guard: &mut QueryComputationGuard, + execution: &mut QueryAttemptExecution, + _origin: &mut Option, + attempt_work: &mut Option, + ) -> Result, CompileErrors> { + self.require_closed_discovery()?; + let parsed = self.published.clone().ok_or_else(no_published_program)?; + *execution = QueryAttemptExecution::Computed; + guard.started(); + let runtime_revision = self + .queries + .revisioned + .last_good_parse_record() + .expect("merge has a successful parse terminal") + .runtime_revision; + let projected_indexes = { + let _span = tracing::info_span!("module_index_projection").entered(); + self.queries + .revisioned + .projected_module_indexes(runtime_revision, &parsed) + }; + // Freeze the traversal work before the fallible duplicate/definition + // checks so deterministic merge failures retain the work already done. + *attempt_work = Some(CanonicalMergeWork { + modules_visited: parsed.modules().len(), + items_visited: parsed + .modules() + .iter() + .map(|module| module.ast().items.len()) + .sum(), + candidates_visited: projected_indexes.as_ref().map_or(0, |indexes| { + indexes.iter().map(|index| index.definitions.len()).sum() + }), + ..CanonicalMergeWork::default() + }); + guard.accrue(QueryStructuralWork::Merge( + attempt_work.expect("merge prefix just installed"), + )); + let batch_order = self + .batch_diagnostic_order + .as_ref() + .map(crate::shared_segments::SharedList::as_arc); + let merged = { + let _span = tracing::info_span!("canonical_merge").entered(); + projected_indexes + .and_then(|indexes| { + merge_parsed_modules_reusing_indexes( + &parsed, + &indexes, + self.definition_shard_baseline.as_ref(), + batch_order.as_deref(), + ) + }) + .map(Arc::new) + }; + if self.cancel_merge_at_commit_boundary() { + guard.request_cancel(); + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput("merge query canceled before commit".into()), + ))); + } + if let Ok(merged) = &merged { + debug_assert_eq!(merged.ast().source_revision(), parsed.source_revision()); + *attempt_work = Some(merged.work()); + guard.accrue(QueryStructuralWork::Merge(merged.work())); } - } - - #[test] - fn anonymous_comptime_producer_failure_is_a_deterministic_diagnostic() { - let source = SourceSnapshot::single( - "main.rue", - "fn empty() -> type {\n struct { }\n}\nfn main() -> i32 {\n let E = empty();\n 0\n}", - ) - .unwrap(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - - let first = session - .canonical_semantic(&CompileOptions::default()) - .expect_err("empty anonymous struct is rejected"); - let second = session - .canonical_semantic(&CompileOptions::default()) - .expect_err("the retained producer failure remains deterministic"); - - assert_eq!(first, second); - assert_eq!(first.len(), 1, "unexpected diagnostics: {first:?}"); - let diagnostic = first.first().unwrap(); - assert!( - matches!(&diagnostic.kind, ErrorKind::EmptyStruct), - "producer failure must remain a source diagnostic, not request cancellation: {diagnostic:?}" + if let Ok(merged) = &merged { + self.definition_shard_baseline = Some(merged.definitions().clone()); + } + let source = self + .published_snapshot + .clone() + .expect("published program retains source snapshot"); + let diagnostics = self.publish_diagnostics( + &source, + FrontendDiagnosticIdentity::Merge, + merged.as_ref().err(), + &[], ); + guard.attach_diagnostics(diagnostics.clone()); + self.diagnostics.select_snapshot(&diagnostics); + self.refresh_retention_metrics(); + merged } - #[test] - fn keyed_destructor_validity_preserves_production_diagnostics_and_spans() { - for (source_text, marker, message) in [ - ( - "drop fn Missing(self) {}\nfn main() {}", - "drop fn Missing(self) {}", - "unknown type 'Missing' in destructor", - ), - ( - "struct S {}\ndrop fn S(self) {}\ndrop fn S(self) {}\nfn main() {}", - "drop fn S(self) {}", - "duplicate destructor for type 'S'", - ), - ] { - let declaration_start = source_text - .rfind(marker) - .expect("fixture contains the rejected destructor"); - let expected = rue_span::Span::with_file( - FileId::new(1), - declaration_start as u32, - (declaration_start + marker.len()) as u32, - ); - let source = snapshot(&[(1, "/p/main.rue", "main.rue", source_text)], 1); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - let error = errors.first().expect("destructor validity diagnostic"); - assert!( - error.to_string().contains(message), - "unexpected diagnostic: {error}" - ); - assert_eq!(error.span(), Some(expected)); - } + /// Query the canonical RIR through an immutable, owner-retaining view. + pub fn rir(&mut self) -> Result, CompileErrors> { + self.canonical_rir().map(crate::RirView::new).map(Arc::new) } - /// Generate `QUERY_TERMINAL_RETENTION_LIMIT + 1` distinct `CompileOptions` - /// for terminal retention/eviction tests (one more key than the store can - /// hold, forcing exactly one eviction). The low `feature_bits` bits of each - /// mask pick a preview-feature subset; the remaining high bits index the - /// compile target, so every mask is a distinct `(preview_features, target)` - /// pair. Both dimensions are part of the semantic, definition, and - /// dependency-manifest query keys — unlike `opt_level`, which keys only the - /// semantic store — so the variants force one eviction in every terminal - /// family under test. - /// - /// With `2` preview features the mask spans four subsets, and three targets - /// carry them to `4 * 3 = 12` distinct keys, exactly `LIMIT + 1`. The - /// assertion guards that the mask range spans no more `feature_bits`-wide - /// blocks than there are targets; if a future feature-count drop breaks it, - /// `QUERY_TERMINAL_RETENTION_LIMIT` must fall so `LIMIT + 1` still fits the - /// available `(preview_features, target)` key space. - fn retention_variants() -> Vec { - let feature_bits = PreviewFeature::all().len(); - let targets = Target::all(); - assert!( - (QUERY_TERMINAL_RETENTION_LIMIT >> feature_bits) < targets.len(), - "retention variants need a distinct (preview_features, target) key per mask" - ); - (0..=QUERY_TERMINAL_RETENTION_LIMIT) - .map(|mask| CompileOptions { - preview_features: PreviewFeature::all() - .iter() - .enumerate() - .filter(|(bit, _)| mask & (1 << bit) != 0) - .map(|(_, feature)| *feature) - .collect(), - target: targets[mask >> feature_bits], - ..CompileOptions::default() - }) - .collect() + pub(crate) fn canonical_rir(&mut self) -> Result, CompileErrors> { + let mut guard = self.metrics.begin::(); + let attempt_id = guard.id; + let mut execution = QueryAttemptExecution::Rejected; + let mut origin = None; + let mut attempt_work = None; + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.rir_attempt( + attempt_id, + &mut guard, + &mut execution, + &mut origin, + &mut attempt_work, + ) + })); + let result = match result { + Ok(result) => result, + Err(payload) => self.resume_canceled_query(&mut guard, payload), + }; + let structural = attempt_work + .map(QueryStructuralWork::Rir) + .unwrap_or(QueryStructuralWork::None); + guard.finish(execution, origin, &result, structural); + self.metrics.synchronize(); + result } - #[test] - fn caught_session_cancellation_is_immediately_observable_and_commits_no_cache() { - let mut session = CompilerSession::new(); - session.update(&base()).into_result().unwrap(); - session.cancel_merge_before_commit = true; - let canceled = session.merge(); - assert!(canceled.is_err()); - assert_eq!(session.queries.merge.len(), 0); - assert_eq!(session.work().merge.calls, 1); - assert_eq!(session.work().merge.executions, 1); - let attempt = session - .metrics - .attempts() - .into_iter() - .find(|attempt| attempt.family == "merge") - .unwrap(); - assert_eq!( - attempt.attempt.outcome(), - crate::typed_query_store::AttemptOutcomeKind::Aborted(AbortedQueryReason::Canceled) - ); - assert_eq!(attempt.attempt.execution(), QueryAttemptExecution::Computed); - // Parse is runtime-owned; the legacy Merge adapter observes only the - // exact source leaf rather than recreating a peer parse graph node. - assert_eq!(attempt.attempt.dependencies().len(), 1); - assert!(attempt.attempt.diagnostics().is_none()); - let QueryStructuralWork::Merge(work) = attempt.attempt.work() else { - panic!("canceled merge must retain exact prefix work"); + fn rir_attempt( + &mut self, + _attempt_id: AttemptId, + guard: &mut QueryComputationGuard, + execution: &mut QueryAttemptExecution, + _origin: &mut Option, + attempt_work: &mut Option, + ) -> Result, CompileErrors> { + self.require_successful_import_diagnostics()?; + let source = self + .published + .as_ref() + .ok_or_else(no_published_program)? + .source_revision() + .clone(); + let merged = self.merge(); + let result = match &merged { + Ok(merged) => { + *execution = QueryAttemptExecution::Computed; + guard.started(); + let revision = self + .queries + .revisioned + .last_good_parse_record() + .expect("published syntax has a successful parse terminal") + .runtime_revision; + let module_ids = merged + .ast() + .modules() + .iter() + .map(|module| module.module_id().clone()) + .collect::>(); + let (module_rirs, query_work) = { + let _span = tracing::info_span!("module_rir_lowering").entered(); + self.queries.revisioned.module_rirs(revision, module_ids) + }; + match module_rirs { + Ok(modules) => { + let projected = { + let _span = tracing::info_span!("rir_projection").entered(); + project_module_rirs_with_work(merged, &modules, query_work) + }; + match projected { + Ok(rir) => { + let rir = Arc::new(rir); + *attempt_work = Some(rir.work()); + guard.accrue(QueryStructuralWork::Rir(rir.work())); + Ok(rir) + } + Err((error, work)) => { + *attempt_work = Some(work); + guard.accrue(QueryStructuralWork::Rir(work)); + Err(CompileErrors::from(error)) + } + } + } + Err(errors) => { + *attempt_work = Some(query_work); + guard.accrue(QueryStructuralWork::Rir(query_work)); + Err(errors) + } + } + } + Err(errors) => Err(errors.clone()), }; - assert_eq!(work.modules_visited, 2); - assert_eq!(work.items_visited, 2); - - session.merge().unwrap(); - assert_eq!(session.queries.merge.len(), 1); - assert_eq!(session.work().merge.calls, 2); - assert_eq!(session.work().merge.executions, 2); + if let Ok(rir) = &result { + debug_assert_eq!(rir.source_revision(), &source); + } + let source_snapshot = self + .published_snapshot + .clone() + .expect("RIR query retains its exact source snapshot"); + let diagnostics = Arc::new(FrontendDiagnosticSnapshot { + source: source_snapshot, + stage: FrontendDiagnosticIdentity::Rir(source.clone()), + provenance: DiagnosticAttemptProvenance::Canonical, + errors: result + .as_ref() + .err() + .map_or_else(|| Arc::from([]), |errors| errors.as_slice().to_vec().into()), + warnings: Arc::from([]), + }); + guard.attach_diagnostics(diagnostics.clone()); + self.diagnostics.select_snapshot(&diagnostics); + self.refresh_retention_metrics(); + result } - #[test] - fn semantic_cancellation_is_control_flow_before_work_and_after_dependencies() { - let source = base(); - let options = CompileOptions::default(); - - let mut prework = CompilerSession::new(); - prework.update(&source).into_result().unwrap(); - let canceled = rue_query::CancellationToken::new(); - canceled.cancel(); - assert!(matches!( - prework.canonical_semantic_with_cancellation(&options, canceled), - Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled - )) - )); - assert_eq!(prework.queries.semantic.len(), 0); - prework.canonical_semantic(&options).unwrap(); + /// Analyze the current published revision without issuing stable definition IDs. + /// Query semantic analysis and optimized CFGs through immutable views. + pub fn semantic( + &mut self, + options: &CompileOptions, + ) -> Result, CompileErrors> { + if self.oracle_fault.take() == Some(crate::unstable::DifferentialOracleFault::Semantic) { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InternalError("differential semantic fault".into()), + ))); + } + let owner = self.canonical_semantic(options)?; + let rir = owner.rir_owner().clone(); + Ok(Arc::new(crate::SemanticView::new(owner, rir))) + } - let mut after_dependency = CompilerSession::new(); - after_dependency.update(&source).into_result().unwrap(); - after_dependency.cancel_semantic_after_dependency = true; - assert!(matches!( - after_dependency.canonical_semantic_with_cancellation( - &options, - rue_query::CancellationToken::new(), - ), - Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled - )) - )); - assert_eq!(after_dependency.queries.semantic.len(), 0); - assert_eq!(after_dependency.queries.rir.len(), 1); - after_dependency.canonical_semantic(&options).unwrap(); + pub(crate) fn canonical_semantic( + &mut self, + options: &CompileOptions, + ) -> Result, CompileErrors> { + match self + .canonical_semantic_with_cancellation(options, rue_query::CancellationToken::new()) + { + Ok(output) => Ok(output), + Err(SemanticRequestControl::Compile(errors)) => Err(errors), + // `canonical_semantic` is a stable no-filesystem entry: it cannot + // acquire trusted toolchain modules, so it converts an unsatisfied + // park to its own error result at this outer boundary (RUE-1112). + // The park-aware host driver uses `semantic_or_toolchain_park`, which + // surfaces the park distinctly and retries after acquisition. + Err(SemanticRequestControl::Parked(park)) => { + Err(unresolved_toolchain_park_errors(&park)) + } + Err(SemanticRequestControl::Abort(abort)) => { + panic!("uncanceled semantic request aborted: {abort:?}") + } + } } - #[test] - fn semantic_cancellation_preserves_completed_dependencies_and_last_good() { - let mut session = CompilerSession::new(); - session.update(&base()).into_result().unwrap(); - let default = CompileOptions::default(); - session.canonical_semantic(&default).unwrap(); - let diagnostics = session.latest_diagnostics().unwrap().clone(); - let last_good = session.last_good_semantic_diagnostics().unwrap().clone(); - let edited = snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, - ); - session.update(&edited).into_result().unwrap(); - let merge_terminals = session.queries.merge.len(); - let rir_terminals = session.queries.rir.len(); - let rir_attempts = session - .metrics - .attempts() - .into_iter() - .filter(|attempt| attempt.family == "rir") - .count(); - assert_eq!(session.queries.semantic.len(), 1); - - let mut variant = default.clone(); - variant.opt_level = OptLevel::O1; - session.cancel_semantic_before_publication = true; - let cancellation = rue_query::CancellationToken::new(); - assert!(matches!( - session.canonical_semantic_with_cancellation(&variant, cancellation), - Err(SemanticRequestControl::Abort( - rue_query::QueryAbort::Canceled - )) - )); - assert_eq!(session.queries.semantic.len(), 1); - assert!(!Arc::ptr_eq( - session.latest_diagnostics().unwrap(), - &diagnostics - )); - assert!(matches!( - session.latest_diagnostics().unwrap().identity(), - FrontendDiagnosticIdentity::Rir(_) - )); - assert!(session.queries.merge.len() > merge_terminals); - assert!(session.queries.rir.len() > rir_terminals); - assert!( - session - .metrics - .attempts() - .into_iter() - .filter(|attempt| attempt.family == "rir") - .count() - > rir_attempts - ); - assert!(Arc::ptr_eq( - session.last_good_semantic_diagnostics().unwrap(), - &last_good - )); - assert_eq!(session.work().semantic.calls, 2); - assert_eq!(session.work().semantic.executions, 2); - assert_eq!(session.work().semantic_entries, 1); - let canceled = session - .metrics - .attempts() + /// Collect the canonical per-function backend terminals for one semantic + /// result. This is deliberately a deterministic adapter: `CodegenUnit` + /// owns lowering, allocation, scheduling, emission, and requested + /// presentation projections; callers only order and project terminals. + pub(crate) fn codegen_products( + &mut self, + semantic: &crate::CanonicalSemanticOutput, + foreign_symbols: &[String], + options: &crate::CompileOptions, + request: rue_codegen::BackendArtifactRequest, + ) -> Result, crate::CompileErrors> { + Ok(self + .codegen_units(semantic, foreign_symbols, options, request)? .into_iter() - .rev() - .find(|attempt| attempt.family == "semantic") - .unwrap(); - assert_eq!( - canceled.attempt.outcome(), - crate::typed_query_store::AttemptOutcomeKind::Aborted(AbortedQueryReason::Canceled) - ); - assert!(matches!(canceled.attempt.work(), QueryStructuralWork::None)); + .map(|collected| collected.unit.backend_product()) + .collect()) + } - let recomputed = session.canonical_semantic(&variant).unwrap(); - assert_eq!(session.queries.semantic.len(), 2); - assert!(Arc::ptr_eq( - &session.canonical_semantic(&variant).unwrap(), - &recomputed + /// Collect reached canonical codegen terminals without immediately + /// collapsing them into the historical backend-product representation. + /// Object and link consumers aggregate this exact result in a + /// `ProgramImagePlan`; presentation consumers may still use the thin + /// `codegen_products` projection above. + pub(crate) fn codegen_units( + &mut self, + semantic: &crate::CanonicalSemanticOutput, + foreign_symbols: &[String], + options: &crate::CompileOptions, + request: rue_codegen::BackendArtifactRequest, + ) -> Result, crate::CompileErrors> { + let revision = self + .queries + .revisioned + .current_semantic_revision() + .ok_or_else(|| { + crate::CompileErrors::from(crate::CompileError::without_span( + crate::ErrorKind::InvalidCompilerInput( + "code generation requires a published semantic revision".into(), + ), + )) + })?; + let mappings = Arc::new(crate::backend::foreign_call_symbol_mappings( + semantic.functions(), + foreign_symbols, )); - assert_eq!(session.work().semantic.executions, 3); - assert_eq!(session.work().semantic.reuses, 1); + let foreign: Arc> = + Arc::new(foreign_symbols.iter().cloned().collect()); + let strings: Arc<[String]> = semantic.strings().to_vec().into(); + let interner = semantic.rir_owner().semantic_symbols().shared_interner(); + let mut units = Vec::with_capacity(semantic.functions().len()); + #[cfg(test)] + self.codegen_executions.clear(); + for function in semantic.functions() { + let attempt = self + .queries + .revisioned + .codegen_unit( + revision, + function.clone(), + semantic.type_pool().clone(), + strings.clone(), + interner.clone(), + mappings.clone(), + foreign.clone(), + function.optimized_cfg_key.clone(), + options.target, + request, + options.opt_level, + rue_query::CancellationToken::new(), + ) + .map_err(|abort| { + crate::CompileErrors::from(crate::CompileError::without_span( + crate::ErrorKind::InternalError(format!( + "codegen query aborted: {abort:?}" + )), + )) + })?; + #[cfg(test)] + self.codegen_executions + .push((function.semantic_identity.clone(), attempt.execution())); + let terminal = attempt.into_result().map_err(|abort| { + crate::CompileErrors::from(crate::CompileError::without_span( + crate::ErrorKind::InternalError(format!("codegen query aborted: {abort:?}")), + )) + })?; + let rue_query::QueryOutcome::Success(unit) = terminal.outcome() else { + unreachable!("CodegenUnit publishes typed terminals") + }; + match unit { + crate::codegen_query::CodegenUnitValue::Available(unit) => { + units.push(crate::codegen_query::CollectedCodegenUnit { + function: function.clone(), + unit: unit.clone(), + }); + } + crate::codegen_query::CodegenUnitValue::Failure(errors) => { + return Err(errors.clone()); + } + } + } + Ok(units) } - fn evict_diagnostic_index(session: &mut CompilerSession) { - for revision in 0..=FRONTEND_DIAGNOSTIC_RETENTION_LIMIT { - let source = snapshot( - &[( - 91, - "/eviction/main.rue", - "main.rue", - &format!("fn main() -> i32 {{ {revision} }}"), - )], - 91, - ); - let snapshot = Arc::new(FrontendDiagnosticSnapshot { - source, - stage: FrontendDiagnosticIdentity::Syntax, - provenance: DiagnosticAttemptProvenance::Canonical, - errors: Arc::from([]), - warnings: Arc::from([]), - }); - session.diagnostics.select_test_snapshot(snapshot); - } + #[cfg(test)] + pub(crate) fn codegen_executions( + &self, + ) -> &[(crate::FunctionInstanceKey, rue_query::RequestExecution)] { + &self.codegen_executions } - /// Publish `source` and, when it imports, commit its graph through a real - /// discovery epoch served from the fixture's own modules. The returned work - /// is the epoch's accumulated parse work, which is the parse an - /// import-bearing revision actually performs. - fn publish_with_test_imports( - session: &mut CompilerSession, - source: &SourceSnapshot, - ) -> ParsedModulesWork { - if !crate::test_support::fixture_has_imports(source).unwrap() { - let update = session.update(source); - let work = update.work(); - update.into_owner_result().unwrap(); - return work; + /// Analyze the current revision, surfacing an unsatisfied trusted-toolchain + /// park distinctly instead of converting it to an error (RUE-1112). This + /// is the rooted, park-aware entry the host compile driver retries: on + /// [`SemanticParkOutcome::Parked`] it acquires exactly the demanded modules, + /// publishes a successor, and calls this again. + pub(crate) fn semantic_or_toolchain_park( + &mut self, + options: &CompileOptions, + ) -> SemanticParkOutcome { + match self + .canonical_semantic_with_cancellation(options, rue_query::CancellationToken::new()) + { + Ok(owner) => { + let rir = owner.rir_owner().clone(); + SemanticParkOutcome::Ready(Arc::new(crate::SemanticView::new(owner, rir))) + } + Err(SemanticRequestControl::Compile(errors)) => SemanticParkOutcome::Errors(errors), + Err(SemanticRequestControl::Parked(park)) => { + // Atomically attach this rooted park's exact sorted missing-demand + // set to the outstanding closed continuation, making it authorizing + // (RUE-1112). Demand authority lives only here — bound to this + // closed revision and this park — so a later, non-parking close can + // never inherit it, and `publish_trusted_toolchain_successor` can + // require the successor's added set to EQUAL exactly this set. + if let Some(state) = self.continuation.as_mut() { + let mut demands = park.demands().to_vec(); + demands.sort(); + demands.dedup(); + state.attached_demands = Some(Arc::from(demands)); + } + SemanticParkOutcome::Parked(park) + } + Err(SemanticRequestControl::Abort(abort)) => { + panic!("uncanceled semantic request aborted: {abort:?}") + } } - crate::test_support::TestDiscoveryHost::new(source) - .unwrap() - .drive(session) - .unwrap() - .parse_work } - fn legacy_air_declaration_shell_oracle( - merged: &CanonicalMergedProgram, - rir: &CanonicalRirOutput, - preview_features: PreviewFeatures, - ) -> Result, CompileErrors> { - let shells = rue_air::Sema::new_synthetic( - rir.rir(), - rir.semantic_symbols().interner(), - preview_features, - ) - .predeclare_declaration_shells_for_test()? - .declaration_shells() - .cloned() - .collect::>(); - let modules = merged - .ast() - .modules() - .iter() - .map(|module| (module.file_id(), module.module_id().clone())) - .collect::>(); - let mut shells = shells - .into_iter() - .map(|mut shell| { - let module = &modules[&shell.declaration_span.file_id]; - shell.identity.module_path = Arc::from(module.as_str()); - shell.identity.is_trusted_standard_library = module.is_trusted_standard_library(); - shell.signature_fingerprint = [0; 32]; - shell - }) - .collect::>(); - sort_shell_comparison_algebra(&mut shells); - Ok(shells) + fn canonical_semantic_with_cancellation( + &mut self, + options: &CompileOptions, + cancellation: rue_query::CancellationToken, + ) -> Result, SemanticRequestControl> { + let mut guard = self.metrics.begin::(); + let attempt_id = guard.id; + let mut execution = QueryAttemptExecution::Rejected; + let mut origin = None; + let mut attempt_record = None; + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.semantic_attempt( + options, + &cancellation, + attempt_id, + &mut guard, + &mut execution, + &mut origin, + &mut attempt_record, + ) + })); + let result = match result { + Ok(result) => result, + Err(payload) => self.resume_canceled_query(&mut guard, payload), + }; + // A trusted-toolchain park exits the attempt before the body transaction + // publishes a terminal, leaving the semantic query in-flight. Clear that + // in-flight attempt exactly as an abort would, so the host driver's retry + // (after acquiring the demanded module and re-closing on a new revision) + // begins a fresh selection instead of colliding with a stale computing + // key (RUE-1112). + if matches!( + result, + Err(SemanticRequestControl::Abort(_)) | Err(SemanticRequestControl::Parked(_)) + ) { + guard.request_cancel(); + } + let structural = attempt_record + .clone() + .map(Box::new) + .map(QueryStructuralWork::Semantic) + .unwrap_or(QueryStructuralWork::None); + guard.finish(execution, origin, &result, structural); + self.metrics.publish_semantic(0); + self.metrics.synchronize(); + result } - fn sort_shell_comparison_algebra(shells: &mut [rue_air::SemanticDeclarationShell]) { - let mut spans_by_file = HashMap::>::new(); - for shell in shells.iter() { - spans_by_file - .entry(shell.declaration_span.file_id) - .or_default() - .push(shell.declaration_span); + fn semantic_attempt( + &mut self, + options: &CompileOptions, + cancellation: &rue_query::CancellationToken, + _attempt_id: AttemptId, + guard: &mut QueryComputationGuard, + execution: &mut QueryAttemptExecution, + _origin: &mut Option, + attempt_record: &mut Option, + ) -> Result, SemanticRequestControl> { + if cancellation.is_canceled() { + return Err(SemanticRequestControl::Abort( + rue_query::QueryAbort::Canceled, + )); + } + self.require_successful_import_diagnostics()?; + let imports = self.accepted_semantic_import_graph()?; + if cancellation.is_canceled() { + return Err(SemanticRequestControl::Abort( + rue_query::QueryAbort::Canceled, + )); + } + let source = self + .published_snapshot + .clone() + .expect("semantic query retains its exact source snapshot"); + let input = CodegenInputDescriptor { + semantic: SemanticInputDescriptor::new( + &source, + options.target, + &options.preview_features, + ), + opt_level: options.opt_level.into(), + }; + let rir_result = self.canonical_rir(); + if cancellation.is_canceled() { + return Err(SemanticRequestControl::Abort( + rue_query::QueryAbort::Canceled, + )); } - let mut normalized_source_order = HashMap::new(); - for spans in spans_by_file.values_mut() { - spans.sort_by_key(|span| (span.start, span.end)); - spans.dedup(); - for (source_order, span) in spans.iter().enumerate() { - normalized_source_order.insert(*span, source_order as u32); + let rir = match rir_result { + Ok(rir) => rir, + Err(errors) => { + let record = SemanticQueryRecord { + input: input.clone(), + work: CanonicalSemanticWork::default(), + failure: None, + failed: true, + }; + guard.accrue(QueryStructuralWork::Semantic(Box::new(record.clone()))); + *attempt_record = Some(record); + let diagnostics = self.publish_diagnostics( + &source, + FrontendDiagnosticIdentity::Semantic(semantic_diagnostic_input( + &input, + imports.clone(), + )), + Some(&errors), + &[], + ); + guard.attach_diagnostics(diagnostics.clone()); + self.diagnostics.select_snapshot(&diagnostics); + self.refresh_retention_metrics(); + return Err(SemanticRequestControl::Compile(errors)); } + }; + let merged = self.merge().map_err(SemanticRequestControl::Compile)?; + #[cfg(test)] + if std::mem::take(&mut self.cancel_semantic_after_dependency) { + cancellation.cancel(); + } + if cancellation.is_canceled() { + return Err(SemanticRequestControl::Abort( + rue_query::QueryAbort::Canceled, + )); } - for shell in shells.iter_mut() { - shell.source_order = normalized_source_order[&shell.declaration_span]; + *execution = QueryAttemptExecution::Computed; + guard.started(); + let runtime_revision = self + .queries + .revisioned + .current_semantic_revision() + .expect("semantic preparation observes a published source/import revision"); + let declaration_shells_span = + tracing::info_span!("declaration_shells", phase = "semantic_analysis").entered(); + let query_shells = { + let _span = tracing::info_span!("declaration_shell_projection").entered(); + self.queries.revisioned.projected_declaration_shells( + runtime_revision, + merged.ast(), + cancellation.clone(), + ) + }; + let mut prepared = match query_shells { + Ok(query_shells) => { + let _span = tracing::info_span!("declaration_shell_prepare").entered(); + prepare_query_declaration_shells(&merged, &rir, options, &imports, &query_shells) + } + Err(crate::revisioned_query_database::DeclarationShellBatchFailure::Query(abort)) => { + return Err(SemanticRequestControl::Abort(abort)); + } + Err(crate::revisioned_query_database::DeclarationShellBatchFailure::Stable( + failure, + )) => Err(CanonicalSemanticFailure::declaration( + declaration_shell_failure_diagnostics(merged.ast(), &failure), + CanonicalSemanticWork::default(), + )), + }; + drop(declaration_shells_span); + let declaration_semantics = { + let _span = tracing::info_span!( + "declaration_semantics_projection", + phase = "semantic_analysis" + ) + .entered(); + self.queries.revisioned.projected_declaration_semantics( + runtime_revision, + merged.ast(), + options.target, + &options.preview_features, + cancellation.clone(), + ) + }; + let ( + query_declarations, + query_anonymous_nominals, + query_declaration_dependencies, + query_c_export_roots, + ) = match declaration_semantics { + Ok(projection) => ( + Some(projection.declarations), + projection.anonymous_nominals, + projection.dependencies, + projection.c_export_roots, + ), + Err(crate::revisioned_query_database::SemanticNucleusBatchFailure::Query(abort)) => { + return Err(SemanticRequestControl::Abort(abort)); + } + Err(crate::revisioned_query_database::SemanticNucleusBatchFailure::Stable { + declaration, + failure, + }) => { + let work = match &prepared { + Ok(prepared) => CanonicalSemanticWork { + declaration_index: prepared.declaration_index_work(), + ..CanonicalSemanticWork::default() + }, + Err(preparation_failure) => preparation_failure.failure.work, + }; + prepared = Err(CanonicalSemanticFailure::declaration( + semantic_nucleus_failure_diagnostics( + merged.ast(), + declaration.as_ref(), + &failure, + ), + work, + )); + (None, Arc::from([]), Arc::from([]), Arc::from([])) + } + }; + if let (Some(declarations), Ok(prepared_definitions)) = + (query_declarations.as_deref(), prepared.as_ref()) + && let Some(main) = prepared_definitions + .definitions() + .definitions() + .iter() + .find(|record| { + let key = record.stable_key(); + key.kind() == crate::StableDefinitionKind::Function + && key.name() == "main" + && key.module() == merged.ast().root() + }) + && let Some(declaration) = declarations + .iter() + .find(|declaration| declaration.key == *main.stable_key()) + && let crate::durable_semantics::DurableDeclarationPayload::Callable { + parameters, + result, + .. + } = &declaration.payload + { + let reason = if !parameters.is_empty() { + Some("`main` must not declare parameters") + } else if !matches!( + result, + crate::durable_semantics::DurableType::I32 + | crate::durable_semantics::DurableType::Unit + ) { + Some("`main` must return `i32` or `()`") + } else { + None + }; + if let Some(reason) = reason { + let work = CanonicalSemanticWork { + declaration_index: prepared_definitions.declaration_index_work(), + ..CanonicalSemanticWork::default() + }; + prepared = Err(CanonicalSemanticFailure::declaration( + CompileErrors::from(CompileError::new( + ErrorKind::InvalidMainSignature { reason }, + main.declaration_span(), + )), + work, + )); + } } - shells.sort_by(|left, right| { - let category = |shell: &rue_air::SemanticDeclarationShell| { - u8::from(shell.identity.namespace != rue_air::StableDefinitionNamespace::Type) + let durable_body_work = crate::DurableBodyWork::default(); + let mut durable_body_candidates = Vec::new(); + let mut durable_specialized_body_candidates = Vec::new(); + let mut durable_anonymous_body_candidates = Vec::new(); + let mut queried_body_work = rue_air::BodyAnalysisWork::default(); + let mut body_produced_anonymous = BTreeMap::new(); + let mut body_consulted_anonymous = BTreeMap::new(); + let mut body_query_errors = BTreeMap::new(); + let mut body_query_reference_cache = BTreeMap::new(); + let mut demanded_drop_glue: Arc<[crate::TypeInstanceKey]> = Arc::from([]); + let mut demanded_drop_glue_plans: Arc< + [(crate::TypeInstanceKey, crate::type_queries::DropGlueFacts)], + > = Arc::from([]); + // RUE-1027 production boundary: derive the reached callable frontier + // serially from the references owned by each body transaction. Ordinary + // call recursion is worklist state, never a query dependency cycle. + // RUE-1028 replaces this deterministic coordinator with database-owned + // reachability and parallel frontier scheduling. + if let Ok(prepared_definitions) = prepared.as_ref() { + let configuration = crate::semantic_query_nucleus::SemanticQueryConfiguration { + target: options.target, + preview_features: StablePreviewFeatures::new(&options.preview_features), + }; + let mut pending = std::collections::BTreeSet::new(); + for record in prepared_definitions.definitions().definitions() { + let stable = record.stable_key(); + if stable.kind() == crate::StableDefinitionKind::Function + && stable.name() == "main" + && stable.module() == merged.ast().root() + { + pending.insert(crate::FunctionInstanceKey::Definition(stable.clone())); + } + } + pending.extend( + query_c_export_roots + .iter() + .cloned() + .map(crate::FunctionInstanceKey::Definition), + ); + let roots = pending; + let closure_request = match self.queries.revisioned.body_closure( + runtime_revision, + crate::body_query::BodyClosureQueryKey { + modules: merged + .ast() + .modules() + .iter() + .map(|module| module.module_id().clone()) + .collect::>() + .into(), + roots: roots.iter().cloned().collect::>().into(), + configuration: configuration.clone(), + }, + cancellation.clone(), + ) { + Ok(request) => request, + Err(abort) => return Err(SemanticRequestControl::Abort(abort)), }; - category(left) - .cmp(&category(right)) - .then(left.identity.cmp(&right.identity)) - }); - } - - fn independently_expected_signature_fingerprints( - merged: &CanonicalMergedProgram, - ) -> HashMap { - fn prefix(declaration: rue_span::Span, payload: rue_span::Span) -> rue_span::Span { - rue_span::Span::with_file(declaration.file_id, declaration.start, payload.start) - } - fn fingerprint( - module: &crate::parsed_modules::ParsedModule, - spans: &[rue_span::Span], - ) -> [u8; 32] { - let mut hasher = Sha256::new(); - for span in spans { - for token in module.tokens().iter().filter(|token| { - token.span.start >= span.start - && token.span.end <= span.end - && !matches!(token.kind, rue_lexer::TokenKind::Eof) - }) { - let value = match token.kind { - rue_lexer::TokenKind::Ident(symbol) => { - format!("ident:{}", module.resolve_raw_symbol(symbol)) + let closure_terminal = &closure_request.terminal; + let rue_query::QueryOutcome::Success(closure_output) = closure_terminal.outcome() + else { + unreachable!("BodyClosure publishes typed values") + }; + demanded_drop_glue = closure_output.demanded_drop_glue.clone(); + demanded_drop_glue_plans = closure_output.demanded_drop_glue_plans.clone(); + if let Some(park) = &closure_output.parked_toolchain { + return Err(SemanticRequestControl::Parked(Box::new(park.clone()))); + } + body_query_errors.extend(closure_output.scheduling_errors.iter().cloned()); + if let Some(fatal) = &closure_output.fatal { + let (instance, errors) = match fatal { + crate::body_query::BodyClosureFatal::DeclarationFailed { + declaration, + failure, + } => ( + roots.iter().next().cloned(), + semantic_nucleus_failure_diagnostics( + merged.ast(), + declaration.as_ref(), + failure, + ), + ), + crate::body_query::BodyClosureFatal::BodyAvailability { instance, detail } => ( + Some(instance.clone()), + crate::CompileErrors::from(crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "body availability was incomplete for {instance:?}: {detail}" + )), + )), + ), + crate::body_query::BodyClosureFatal::ProducerFailed { instance, failure } => ( + Some(instance.clone()), + semantic_nucleus_failure_diagnostics(merged.ast(), None, failure), + ), + crate::body_query::BodyClosureFatal::WellKnownOptionResolution { + instance, + failure, + } => ( + Some(instance.clone()), + well_known_option_resolution_diagnostics(merged.ast(), failure), + ), + crate::body_query::BodyClosureFatal::TypeQuery { ty, detail } => ( + roots.iter().next().cloned(), + crate::CompileErrors::from(crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "canonical type query failed for {ty:?}: {detail}" + )), + )), + ), + crate::body_query::BodyClosureFatal::AnonymousDigestCollision { + digest, + first, + second, + } => ( + roots.iter().next().cloned(), + crate::CompileErrors::from(crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "stable anonymous symbol digest {digest:032x} is shared by distinct \ + producer-nominal identities {first:?} and {second:?}" + )), + )), + ), + }; + if let Some(instance) = instance { + body_query_errors.insert(instance, errors); + } else { + return Err(SemanticRequestControl::Compile(errors)); + } + } + body_query_reference_cache.clear(); + for closure_body in closure_output.bodies.iter() { + let instance = &closure_body.key.instance; + let key = &closure_body.key; + let rue_query::QueryOutcome::Success(analysis) = closure_body.bundle.outcome() + else { + unreachable!("BodyAnalysisBundle publishes typed values") + }; + let computed = matches!( + closure_request.execution_for(key), + rue_query::RequestExecution::Computed + ); + let had_retained_body = closure_request.was_retained(key); + if computed { + queried_body_work.body_analyses_computed += 1; + } else { + queried_body_work.body_analyses_reused += 1; + } + let transaction = &analysis.transaction; + if computed { + let specialized = + matches!(instance, crate::FunctionInstanceKey::Specialization { .. }); + queried_body_work.bodies_attempted += 1; + if had_retained_body { + queried_body_work.body_analyses_invalidated += 1; + } + if specialized { + queried_body_work.specialized_bodies_attempted += 1; + } + match transaction { + crate::body_query::BodyTransaction::Success { body, .. } => { + queried_body_work.bodies_succeeded += 1; + let body = match body.as_ref() { + crate::body_query::CanonicalBody::Ordinary { body, .. } + | crate::body_query::CanonicalBody::Anonymous { body, .. } + | crate::body_query::CanonicalBody::Specialization { + body, .. + } => body, + }; + queried_body_work.air_instructions_produced += body.instructions.len(); + queried_body_work.local_strings_produced += body.strings.len(); + if specialized { + queried_body_work.specialized_bodies_succeeded += 1; + queried_body_work.specialized_body_exports_attempted += 1; + queried_body_work.specialized_body_exports_succeeded += 1; + queried_body_work.specialized_body_export_instructions_emitted += + body.instructions.len(); + queried_body_work.specialized_body_export_places_emitted += + body.places.len(); + queried_body_work.specialized_body_export_strings_emitted += + body.strings.len(); + } else { + queried_body_work.ordinary_body_exports_attempted += 1; + queried_body_work.ordinary_body_exports_succeeded += 1; + queried_body_work.ordinary_body_export_instructions_emitted += + body.instructions.len(); + queried_body_work.ordinary_body_export_places_emitted += + body.places.len(); + queried_body_work.ordinary_body_export_strings_emitted += + body.strings.len(); + } + } + crate::body_query::BodyTransaction::DeterministicFailure { .. } => { + queried_body_work.bodies_failed += 1; + if specialized { + queried_body_work.specialized_bodies_failed += 1; + } } - rue_lexer::TokenKind::String(symbol) => { - format!("string:{}", module.resolve_raw_symbol(symbol)) + crate::body_query::BodyTransaction::Control(_) => { + unreachable!("body closure unwraps transaction control") } - rue_lexer::TokenKind::Int(value) => format!("int:{value}"), - kind => kind.name().to_owned(), - }; - hasher.update((value.len() as u64).to_le_bytes()); - hasher.update(value.as_bytes()); + } } - hasher.update(0_u64.to_le_bytes()); - } - hasher.finalize().into() - } - - let mut expected = HashMap::new(); - for module in merged.ast().modules() { - for item in &module.ast().items { - match item { - rue_parser::Item::Function(value) => { - expected.insert( - value.span, - fingerprint(module, &[prefix(value.span, value.body.span())]), + body_query_reference_cache + .insert(instance.clone(), transaction.references().clone()); + if let crate::body_query::BodyTransaction::DeterministicFailure { errors, .. } = + transaction + { + body_query_errors.insert(instance.clone(), errors.clone()); + continue; + } + let crate::body_query::BodyTransaction::Success { + body, + consulted_anonymous_nominals, + .. + } = transaction + else { + unreachable!("body closure contains no control transactions") + }; + body_consulted_anonymous.extend( + consulted_anonymous_nominals + .0 + .iter() + .cloned() + .map(|nominal| (nominal.identity.clone(), nominal)), + ); + if let Some(crate::body_query::ProducedAnonymous::Produced(produced)) = + analysis.produced_anonymous.as_ref() + { + body_produced_anonymous.extend( + produced + .0 + .iter() + .cloned() + .map(|nominal| (nominal.identity.clone(), nominal)), + ); + } + match body.as_ref() { + crate::body_query::CanonicalBody::Ordinary { owner, body } => { + let Some(record) = + prepared_definitions.definitions().definition_by_key(owner) + else { + body_query_errors.insert( + instance.clone(), + crate::CompileErrors::from( + crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "reached body {instance:?} published an ordinary body whose owner has no issued definition record" + )), + ), + ), + ); + continue; + }; + let Some(body_span) = record.body_span() else { + body_query_errors.insert( + instance.clone(), + crate::CompileErrors::from( + crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "reached body {instance:?} published an ordinary body whose owner definition record carries no body span" + )), + ), + ), + ); + continue; + }; + durable_body_candidates.push( + crate::canonical_semantic::PreparedDurableBodyCandidate { + owner: owner.clone(), + body_span, + body: body.clone(), + }, ); } - rue_parser::Item::Struct(value) => { - let mut spans = Vec::with_capacity(value.methods.len() + 1); - let mut cursor = value.span.start; - for method in &value.methods { - let body = method.body.span(); - spans.push(rue_span::Span::with_file( - value.span.file_id, - cursor, - body.start, - )); - cursor = body.end; - expected.insert( - method.span, - fingerprint(module, &[prefix(method.span, body)]), + crate::body_query::CanonicalBody::Anonymous { + identity, + body_anchor, + body, + } => { + let crate::FunctionInstanceKey::AnonymousMember { owner, member: _ } = + identity + else { + body_query_errors.insert( + instance.clone(), + crate::CompileErrors::from( + crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "reached body {instance:?} published an anonymous body whose identity is not an anonymous member" + )), + ), + ), + ); + continue; + }; + let crate::TypeInstanceKey::Nominal(crate::NominalInstanceKey::Anonymous( + _, + )) = owner.as_ref() + else { + body_query_errors.insert( + instance.clone(), + crate::CompileErrors::from( + crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "reached body {instance:?} published an anonymous body whose owner is not an anonymous nominal type" + )), + ), + ), ); - } - spans.push(rue_span::Span::with_file( - value.span.file_id, - cursor, - value.span.end, - )); - expected.insert(value.span, fingerprint(module, &spans)); - } - rue_parser::Item::Enum(value) => { - expected.insert(value.span, fingerprint(module, &[value.span])); - } - rue_parser::Item::Const(value) => { - expected.insert( - value.span, - fingerprint(module, &[prefix(value.span, value.init.span())]), - ); - } - rue_parser::Item::DropFn(value) => { - expected.insert( - value.span, - fingerprint(module, &[prefix(value.span, value.body.span())]), - ); - } - rue_parser::Item::Extern(value) => { - for function in &value.fns { - expected.insert(function.span, fingerprint(module, &[function.span])); - } - } - rue_parser::Item::Error(_) => {} - } - } - } - expected - } - - fn function_modules(count: usize, edited: Option) -> SourceSnapshot { - let owned = (0..count) - .map(|index| { - let id = u32::try_from(index + 1).unwrap(); - let logical = if index == 0 { - "main.rue".to_owned() - } else { - format!("m{index}.rue") - }; - let physical = format!("/p/{logical}"); - let body = if index == 0 { - format!( - "fn main() -> i32 {{ {} }}", - usize::from(edited == Some(index)) - ) - } else { - format!( - "fn f{index}() -> i32 {{ {} }}", - if edited == Some(index) { - index + 1 - } else { - index - } - ) - }; - (id, physical, logical, body) - }) - .collect::>(); - let borrowed = owned - .iter() - .map(|(id, physical, logical, body)| { - (*id, physical.as_str(), logical.as_str(), body.as_str()) - }) - .collect::>(); - snapshot(&borrowed, 1) - } - - #[test] - fn leaf_body_edit_reuses_128_durable_declarations_and_skips_ordinary_resolution() { - let options = CompileOptions::default(); - let first = function_modules(128, None); - // Edit the reachable entry body while retaining all 128 declarations; - // this proves reuse does not accidentally pass by changing dead code. - let second = function_modules(128, Some(0)); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - let cold = session.canonical_semantic(&options).unwrap(); - assert_eq!(cold.work().binding.bind_invocations, 1); - assert_eq!(cold.work().binding.declaration_resolution_invocations, 0); - assert_eq!( - cold.work() - .declaration_reuse - .durable_cache_population_exports, - 0 - ); - assert_eq!(cold.work().manifest.rir_instructions_visited, 256); - session.update(&second).into_result().unwrap(); - let reused = session.canonical_semantic(&options).unwrap(); - - assert_eq!(reused.work().binding.declaration_resolution_invocations, 0); - assert_eq!(reused.work().binding.bind_invocations, 1); - assert_eq!( - reused.work().declaration_reuse.declaration_prefixes_built, - 1 - ); - assert_eq!(reused.work().declaration_reuse.declaration_indexes_built, 1); - assert_eq!( - reused - .work() - .declaration_reuse - .declaration_prefix_population_runs, - 1 - ); - assert_eq!( - reused.work().declaration_reuse.declaration_prefix_fallbacks, - 0 - ); - assert_eq!(reused.work().binding.durable_payloads_installed, 128); - assert_eq!(reused.work().declaration_reuse.durable_records_reused, 128); - assert_eq!( - reused - .work() - .declaration_reuse - .ordinary_declaration_resolutions_skipped, - 1 - ); - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let ordinary = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - ordinary.work().binding.declaration_resolution_invocations, - 0 - ); - let warm_body_work = reused.work().body_analysis; - let fresh_body_work = ordinary.work().body_analysis; - assert_eq!(warm_body_work.body_analyses_computed, 1); - assert_eq!(warm_body_work.body_analyses_reused, 0); - assert_eq!(warm_body_work.body_analyses_invalidated, 1); - assert_eq!(fresh_body_work.body_analyses_computed, 1); - assert_eq!(fresh_body_work.body_analyses_reused, 0); - assert_eq!(fresh_body_work.body_analyses_invalidated, 0); - assert_eq!( - warm_body_work.body_analyses_computed, fresh_body_work.body_analyses_computed, - "warm and fresh must compute the same number of bodies" - ); - assert_eq!( - warm_body_work.body_analyses_reused, fresh_body_work.body_analyses_reused, - "warm and fresh must report the same reuse count" - ); - assert_eq!( - warm_body_work.body_analyses_invalidated, - fresh_body_work.body_analyses_invalidated + 1, - "warm has one intentional invalidation delta for its retained predecessor" - ); - assert_eq!( - warm_body_work.bodies_attempted, fresh_body_work.bodies_attempted, - "warm and fresh must attempt the same bodies" - ); - assert_eq!( - warm_body_work.bodies_succeeded, fresh_body_work.bodies_succeeded, - "warm and fresh must succeed for the same bodies" - ); - assert_eq!( - warm_body_work.air_instructions_produced, fresh_body_work.air_instructions_produced, - "warm and fresh must produce the same AIR instruction count" - ); - assert_eq!( - warm_body_work.local_strings_produced, fresh_body_work.local_strings_produced, - "warm and fresh must produce the same local string count" - ); - assert_eq!( - warm_body_work.ordinary_body_exports_attempted, - fresh_body_work.ordinary_body_exports_attempted, - "warm and fresh must attempt the same ordinary exports" - ); - assert_eq!( - warm_body_work.ordinary_body_exports_succeeded, - fresh_body_work.ordinary_body_exports_succeeded, - "warm and fresh must succeed for the same ordinary exports" - ); - assert_eq!( - warm_body_work.specialized_bodies_attempted, - fresh_body_work.specialized_bodies_attempted, - "warm and fresh must attempt the same specialized bodies" - ); - assert_eq!( - warm_body_work.specialized_bodies_succeeded, - fresh_body_work.specialized_bodies_succeeded, - "warm and fresh must succeed for the same specialized bodies" - ); - assert_eq!( - format!("{:?}", reused.functions()), - format!("{:?}", ordinary.functions()) - ); - assert_eq!(reused.strings(), ordinary.strings()); - assert_eq!( - format!("{:?}", reused.warnings()), - format!("{:?}", ordinary.warnings()) - ); - } - - #[test] - fn module_bindings_populate_the_durable_baseline_and_reuse_across_relocation() { - let original = snapshot( - &[ - ( - 71, - "/old/main.rue", - "main.rue", - "const lib = @import(\"lib.rue\"); fn main() -> i32 { lib.value() + 1 }", - ), - ( - 72, - "/old/lib.rue", - "lib.rue", - "pub fn value() -> i32 { 40 }", - ), - ], - 71, - ); - let relocated_edit = snapshot( - &[ - (4, "/new/lib.rue", "lib.rue", "pub fn value() -> i32 { 40 }"), - ( - 9, - "/new/main.rue", - "main.rue", - "const lib = @import(\"lib.rue\"); fn main() -> i32 { lib.value() + 2 }", - ), - ], - 9, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &original); - let cold = session.canonical_semantic(&options).unwrap(); - assert_eq!(cold.work().binding.declaration_resolution_invocations, 0); - assert_eq!( - cold.work() - .declaration_reuse - .durable_cache_population_exports, - 0 - ); - let module = session - .last_good_durable_declaration_cache() - .unwrap() - .semantics - .iter() - .find(|record| record.key.kind() == StableDefinitionKind::ModuleBinding) - .unwrap(); - assert!(matches!( - &module.payload, - crate::DurableDeclarationPayload::ModuleBinding { target } - if target.as_str() == "lib.rue" - )); - - publish_with_test_imports(&mut session, &relocated_edit); - let reused = session.canonical_semantic(&options).unwrap(); - assert_eq!( - reused.work().binding.declaration_resolution_invocations, - 0, - "{:#?}", - reused.work() - ); - assert_eq!(reused.work().binding.durable_payloads_installed, 3); - assert_eq!(reused.work().declaration_reuse.durable_records_reused, 3); - assert_eq!( - reused - .work() - .declaration_reuse - .ordinary_declaration_resolutions_skipped, - 1 - ); - assert_eq!(reused.work().declaration_reuse.fallbacks, 0); - - let mut fresh = CompilerSession::new(); - publish_with_test_imports(&mut fresh, &relocated_edit); - let ordinary = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &reused, &ordinary); - assert_diagnostic_parity(&session, &fresh); - } - - #[test] - fn unresolved_durable_module_target_falls_back_without_installing() { - let source = |body: i32| { - snapshot( - &[ - ( - 1, - "/p/main.rue", - "main.rue", - &format!( - "const lib = @import(\"lib.rue\"); fn main() -> i32 {{ lib.value() + {body} }}" - ), - ), - (2, "/p/lib.rue", "lib.rue", "pub fn value() -> i32 { 40 }"), - ], - 1, - ) - }; - let first = source(1); - let edited = source(2); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &first); - session.canonical_semantic(&options).unwrap(); - // Perturb a copy of the produced baseline and inject it, so the next - // attempt resolves it through the same selection order production uses - // (RUE-1143). - let mut cache = session - .last_good_durable_declaration_cache() - .unwrap() - .clone(); - let mut records = cache.semantics.to_vec(); - let module = records - .iter_mut() - .find(|record| record.key.kind() == StableDefinitionKind::ModuleBinding) - .unwrap(); - module.payload = crate::DurableDeclarationPayload::ModuleBinding { - target: crate::ModuleId::from_logical_path("missing.rue").unwrap(), - }; - cache.semantics = records.into(); - session.set_durable_baseline_override(Some(DurableBaselineOverride { - durable_declaration_cache: Some(cache), - })); - - publish_with_test_imports(&mut session, &edited); - let fallback = session.canonical_semantic(&options).unwrap(); - assert_eq!( - fallback.work().binding.declaration_resolution_invocations, - 0 - ); - assert_eq!(fallback.work().binding.durable_install_invocations, 1); - assert!(fallback.work().binding.durable_payloads_installed > 0); - assert_eq!(fallback.work().declaration_reuse.fallbacks, 0); - assert_eq!( - fallback - .work() - .declaration_reuse - .declaration_prefix_fallbacks, - 0 - ); - - let mut fresh = CompilerSession::new(); - publish_with_test_imports(&mut fresh, &edited); - let ordinary = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &fallback, &ordinary); - assert_diagnostic_parity(&session, &fresh); - } - - #[test] - fn nested_layout_change_invalidates_only_layout_consumers() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Inner { a: i32 }\nstruct Outer { inner: Inner }\nfn consume(value: Outer) -> i32 { value.inner.a }\nfn unaffected() -> i32 { 7 }\nfn main() -> i32 { consume(Outer { inner: Inner { a: 1 } }) + unaffected() }", - )], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Inner { a: i32, b: i32 }\nstruct Outer { inner: Inner }\nfn consume(value: Outer) -> i32 { value.inner.a }\nfn unaffected() -> i32 { 7 }\nfn main() -> i32 { consume(Outer { inner: Inner { a: 1, b: 2 } }) + unaffected() }", - )], - 1, - ); - let options = CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() - }; - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - let consume_key = body_query_key(&mut session, &options, "consume"); - let consume_transaction = retained_body_transaction(&session, &consume_key).2; - assert!( - consume_transaction.references().0.iter().any(|reference| { - matches!( - reference, - crate::body_query::BodyReference::Type(crate::TypeInstanceKey::Nominal( - crate::NominalInstanceKey::Named(definition) - )) if definition.name() == "Inner" - ) - }), - "{consume_transaction:?}" - ); - let dependency_nodes = retained_body_dependency_nodes(&session, &consume_key); - assert!( - dependency_nodes + continue; + }; + let Some(source) = analysis.source_locator.as_ref() else { + body_query_errors.insert( + instance.clone(), + crate::CompileErrors::from( + crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "reached body {instance:?} published an anonymous body without a current source locator" + )), + ), + ), + ); + continue; + }; + let body_span = rue_span::Span::with_file( + source.file_id, + source + .body_start + .saturating_add(body_anchor.start) + .min(source.body_end), + source + .body_start + .saturating_add(body_anchor.end) + .min(source.body_end), + ); + durable_anonymous_body_candidates.push( + crate::canonical_semantic::PreparedDurableAnonymousBodyCandidate { + identity: identity.clone(), + body_span, + body: body.clone(), + }, + ); + } + crate::body_query::CanonicalBody::Specialization { identity, body, .. } => { + let Some(record) = prepared_definitions + .definitions() + .definition_by_key(&identity.base) + else { + body_query_errors.insert( + instance.clone(), + crate::CompileErrors::from( + crate::CompileError::without_span( + rue_error::ErrorKind::InternalError(format!( + "reached body {instance:?} published a specialization body whose base has no issued definition record" + )), + ), + ), + ); + continue; + }; + durable_specialized_body_candidates.push( + crate::canonical_semantic::PreparedDurableSpecializedBodyCandidate { + instance: instance.clone(), + identity: identity.clone(), + body_span: record.declaration_span(), + body: body.clone(), + }, + ); + } + } + } + queried_body_work.closure_bodies_visited = closure_output.bodies.len(); + tracing::info!( + bodies_attempted = queried_body_work.bodies_attempted, + bodies_visited = queried_body_work.closure_bodies_visited, + closure_restarts = queried_body_work.closure_restarts, + deferred_producer_retries = queried_body_work.deferred_producer_retries, + max_specialization_depth = queried_body_work.max_specialization_depth, + "body closure complete" + ); + } + let query_anonymous_nominals = { + let mut all = query_anonymous_nominals .iter() - .any(|node| node.contains("signature") && node.contains("Inner")), - "{dependency_nodes:?}" - ); - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert_eq!(warm.work().cfg.cfg_reuses, 1); - assert_eq!(warm.work().cfg.cfg_builds_attempted, 2); - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", warm.functions()), - format!("{:?}", fresh.functions()) - ); - } - - #[test] - fn pointer_only_consumer_ignores_pointee_layout_but_field_consumer_rebuilds() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Foo { a: i32 }\nfn pointer_only(value: ptr const Foo) -> i32 { 7 }\nfn field(value: Foo) -> i32 { value.a }\nfn main() -> i32 { let value = Foo { a: 1 }; checked { pointer_only(@raw(value)) + field(value) } }", - )], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Foo { a: i32, b: i32 }\nfn pointer_only(value: ptr const Foo) -> i32 { 7 }\nfn field(value: Foo) -> i32 { value.a }\nfn main() -> i32 { let value = Foo { a: 1, b: 2 }; checked { pointer_only(@raw(value)) + field(value) } }", - )], - 1, - ); - let options = CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() - }; - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert_eq!(warm.work().cfg.cfg_reuses, 1); - assert_eq!(warm.work().cfg.cfg_builds_attempted, 2); - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", warm.functions()), - format!("{:?}", fresh.functions()) - ); - } - - #[test] - fn cfg_reuse_is_per_function_and_preserves_exact_build_work() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn a() -> i32 { @dbg(\"same\"); @dbg(\"same\"); @dbg(\"alpha\"); 1 }\n\ - fn b() -> i32 { @dbg(\"beta\"); 2 }\n\ - fn main() -> i32 { @dbg(\"gamma\"); a() + b() }", - )], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "// move every retained body and perturb another body's string projection\n\ - fn a() -> i32 { @dbg(\"same\"); @dbg(\"same\"); @dbg(\"alpha\"); 1 }\n\ - fn b() -> i32 { @dbg(\"delta\"); @dbg(\"beta\"); 3 }\n\ - fn main() -> i32 { @dbg(\"gamma\"); a() + b() }", - )], - 1, - ); - let options = CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() + .cloned() + .map(|nominal| (nominal.identity.clone(), nominal)) + .collect::>(); + all.extend(body_consulted_anonymous); + all.extend(body_produced_anonymous); + Arc::from(all.into_values().collect::>()) }; - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - let cold = session.canonical_semantic(&options).unwrap(); - assert_eq!(cold.work().cfg.cfg_builds_attempted, 3); - assert_eq!(cold.work().cfg.optimization_attempts, 3); - let cold_atoms = cold - .functions() - .iter() - .flat_map(|function| function.local_atoms.iter()) - .collect::>(); - assert_eq!(cold_atoms.len(), 5); - assert_eq!( - cold_atoms - .iter() - .filter(|atom| atom.content.as_ref() == "same") - .count(), - 2 - ); - assert!(cold_atoms.iter().any(|atom| atom.dense_id > 1)); - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert_eq!( - warm.work().cfg.cfg_reuses, - 2, - "cfg work: {:?}", - warm.work().cfg - ); - assert_eq!(warm.work().cfg.cfg_import_successes, 2); - assert_eq!(warm.work().cfg.cfg_builds_attempted, 1); - assert_eq!(warm.work().cfg.optimization_attempts, 1); - assert_eq!(warm.work().cfg.optimized_level_attempts, 1); - for function in warm.functions() { - for atom in &function.local_atoms { - assert_eq!( - warm.strings() - .get(atom.dense_id as usize) - .map(String::as_str), - Some(atom.content.as_ref()) + // Declaration payloads have one production authority: the revisioned + // semantic query nucleus. The old durable declaration cache remains a + // test-oracle fixture only; it must never select or revive a second + // declaration resolver in a real request. + let analysis = prepared.and_then(|prepared| { + if !body_query_errors.is_empty() { + let errors = body_query_errors.into_values().fold( + CompileErrors::new(), + |mut all, errors| { + all.extend(errors); + all + }, ); + let mut work = CanonicalSemanticWork { + declaration_index: prepared.declaration_index_work(), + declaration_reuse: prepared.declaration_reuse_work(), + ..CanonicalSemanticWork::default() + }; + work.accrue_body_query_work(queried_body_work); + return Err(CanonicalSemanticFailure::body(errors, work)); } + let durable = query_declarations + .expect("successful semantic-nucleus projection publishes declaration payloads"); + let definitions = prepared.definitions().clone(); + let mut output = analyze_prepared_canonical_program_reusing_declarations( + &merged, + rir.clone(), + options, + &imports, + prepared, + &definitions, + &durable, + &query_anonymous_nominals, + &query_declaration_dependencies, + durable_body_candidates, + durable_specialized_body_candidates, + durable_anonymous_body_candidates, + demanded_drop_glue, + demanded_drop_glue_plans, + durable_body_work, + &self.queries.revisioned, + runtime_revision, + cancellation.clone(), + )?; + output.accrue_body_query_work(queried_body_work); + Ok(output) + }); + #[cfg(test)] + if std::mem::take(&mut self.cancel_semantic_before_publication) { + cancellation.cancel(); + } + if cancellation.is_canceled() { + return Err(SemanticRequestControl::Abort( + rue_query::QueryAbort::Canceled, + )); + } + let failure = analysis.as_ref().err().map(|failure| failure.failure); + let semantic_work = analysis + .as_ref() + .map(|output| output.work()) + .unwrap_or_else(|failure| failure.failure.work); + let result = analysis.map(Arc::new).map_err(|failure| failure.errors); + let record = SemanticQueryRecord { + input: input.clone(), + work: semantic_work, + failure, + failed: result.is_err(), + }; + guard.accrue(QueryStructuralWork::Semantic(Box::new(record.clone()))); + *attempt_record = Some(record); + if let Ok(output) = &result { + debug_assert_eq!(output.input(), &input); + debug_assert_eq!(semantic_work.binding.bind_invocations, 1); + debug_assert_eq!(semantic_work.manifest.build_invocations, 1); } - - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", warm.functions()), - format!("{:?}", fresh.functions()) - ); - assert_eq!( - format!("{:?}", warm.warnings()), - format!("{:?}", fresh.warnings()) - ); - } - - #[test] - fn cfg_drop_facts_invalidate_same_layout_cleanup_changes() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Resource { value: i32 }\n\ - fn main() -> i32 { let resource = Resource { value: 1 }; 0 }", - )], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Resource { value: i32 }\n\ - drop fn Resource(self) { @dbg(self.value); }\n\ - fn main() -> i32 { let resource = Resource { value: 1 }; 0 }", - )], - 1, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert!( - warm.work().cfg.cfg_builds_attempted > 0, - "same-layout drop-fact changes must rebuild affected CFGs: {:?}", - warm.work().cfg - ); - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - normalize_session_local_spurs(format!("{:?}", warm.functions())), - normalize_session_local_spurs(format!("{:?}", fresh.functions())) + let diagnostic_input = semantic_diagnostic_input(&input, imports.clone()); + let diagnostics = self.publish_diagnostics( + &source, + FrontendDiagnosticIdentity::Semantic(diagnostic_input), + result.as_ref().err(), + result + .as_ref() + .map(|output| output.warnings()) + .unwrap_or(&[]), ); + guard.attach_diagnostics(diagnostics.clone()); + self.diagnostics.select_snapshot(&diagnostics); + self.refresh_retention_metrics(); + result.map_err(SemanticRequestControl::Compile) } - #[test] - fn cfg_relocation_covers_runtime_param_drop_and_nominal_field_domains() { - let program = |prefix: &str| { - format!( - "{prefix}\ - struct Leaf {{ value: i32 }}\n\ - drop fn Leaf(self) {{ @dbg(self.value); }}\n\ - struct Holder {{ leaf: Leaf }}\n\ - fn consume(value: Holder) -> i32 {{\n\ - @dbg(value.leaf.value);\n\ - value.leaf.value\n\ - }}\n\ - fn main() -> i32 {{ consume(Holder {{ leaf: Leaf {{ value: 7 }} }}) }}" + /// Issue stable definition IDs on demand for the current semantic input. + /// + /// The authoritative semantic terminal already owns the final, + /// post-classification definition universe. This projection never starts a + /// peer declaration bind or reconstructs stable IDs from shells. + pub(crate) fn stable_definitions( + &mut self, + options: &CompileOptions, + ) -> Result, CompileErrors> { + let mut guard = self.metrics.begin::(); + let attempt_id = guard.id; + let mut execution = QueryAttemptExecution::Rejected; + let mut origin = None; + let mut attempt_record = None; + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.stable_definitions_attempt( + options, + attempt_id, + &mut guard, + &mut execution, + &mut origin, + &mut attempt_record, ) + })); + let result = match result { + Ok(result) => result, + Err(payload) => self.resume_canceled_query(&mut guard, payload), }; - let first_text = program(""); - let second_text = program( - "struct Noise { pad: i64 }\n\ - fn noise(value: Noise) -> i64 { @assert(value.pad >= 0); value.pad }\n", - ); - let first = snapshot(&[(1, "/p/main.rue", "main.rue", first_text.as_str())], 1); - let second = snapshot(&[(1, "/p/main.rue", "main.rue", second_text.as_str())], 1); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert_eq!( - warm.work().cfg.cfg_builds_attempted, - 0, - "live-domain relocation must not rebuild reusable unoptimized CFGs: {:?}", - warm.work().cfg - ); - assert_eq!( - warm.work().cfg.cfg_reuses, - 5, - "every unchanged reachable CFG must be reused: {:?}", - warm.work().cfg - ); - assert_eq!( - warm.work().cfg.optimization_attempts, - 0, - "complete relocation domains must reuse optimized terminals: {:?}", - warm.work().cfg - ); - assert_eq!( - warm.work().cfg.cfg_import_successes, - warm.work().cfg.cfg_reuses, - "the collector must receive already-relocated optimized terminals: {:?}", - warm.work().cfg - ); - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - normalize_session_local_spurs(format!("{:?}", warm.functions())), - normalize_session_local_spurs(format!("{:?}", fresh.functions())) - ); - assert_eq!( - format!("{:?}", warm.warnings()), - format!("{:?}", fresh.warnings()) - ); + let structural = attempt_record + .clone() + .map(Box::new) + .map(QueryStructuralWork::Definition) + .unwrap_or(QueryStructuralWork::None); + guard.finish(execution, origin, &result, structural); + self.metrics.publish_definition(0); + self.metrics.synchronize(); + result } - #[test] - fn cfg_import_failure_rebuilds_current_body_with_exact_fallback_work() { - let first = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 7 }")], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "// relocate the retained body\nfn main() -> i32 { 7 }", - )], - 1, - ); - let options = CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() + fn stable_definitions_attempt( + &mut self, + options: &CompileOptions, + _attempt_id: AttemptId, + guard: &mut QueryComputationGuard, + execution: &mut QueryAttemptExecution, + _origin: &mut Option, + attempt_record: &mut Option, + ) -> Result, CompileErrors> { + self.require_successful_import_diagnostics()?; + let imports = self.accepted_semantic_import_graph()?; + let snapshot = self + .published_snapshot + .clone() + .expect("definition query retains its exact source snapshot"); + let input = + SemanticInputDescriptor::new(&snapshot, options.target, &options.preview_features); + let _rir = match self.canonical_rir() { + Ok(rir) => rir, + Err(errors) => { + let record = DefinitionQueryRecord { + input: input.clone(), + binding: DeclarationBindingWork::default(), + manifest: SemanticBindingManifestWork::default(), + issuance: BoundDefinitionWork::default(), + failed: true, + }; + guard.accrue(QueryStructuralWork::Definition(Box::new(record.clone()))); + *attempt_record = Some(record); + self.refresh_retention_metrics(); + return Err(errors); + } }; - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - - session.update(&second).into_result().unwrap(); - let warm = crate::canonical_semantic::with_test_cfg_import_failure_injection(|| { - session.canonical_semantic(&options).unwrap() - }); - assert_eq!(warm.work().cfg.cfg_reuse_candidates, 1); - assert_eq!(warm.work().cfg.cfg_reuses, 0); - assert_eq!(warm.work().cfg.cfg_import_attempts, 1); - assert_eq!(warm.work().cfg.cfg_import_successes, 0); - assert_eq!(warm.work().cfg.cfg_import_failures, 1); - assert_eq!(warm.work().cfg.cfg_fallbacks, 1); - assert_eq!(warm.work().cfg.cfg_builds_attempted, 1); - assert_eq!(warm.work().cfg.cfg_builds_succeeded, 1); - assert_eq!(warm.work().cfg.cfg_builds_failed, 0); - assert_eq!(warm.work().cfg.optimization_attempts, 1); - assert_eq!(warm.work().cfg.optimization_completions, 1); - assert_eq!(warm.work().cfg.optimized_level_attempts, 1); - - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - normalize_session_local_spurs(format!("{:?}", warm.functions())), - normalize_session_local_spurs(format!("{:?}", fresh.functions())) - ); - } - - #[test] - fn reused_parse_runtime_symbol_relocates_to_the_current_interner() { - let program = |prefix: &str| { - format!( - "{prefix}\ - const opt = @import(\"std/option.rue\");\n\ - fn parse_runtime() -> i32 {{ let _ = @parse_i64(\"7\"); 0 }}\n\ - fn main() -> i32 {{ parse_runtime() }}" - ) + let merged = self.merge()?; + let semantic = match self.canonical_semantic(options) { + Ok(semantic) => semantic, + Err(errors) => { + let record = DefinitionQueryRecord { + input: input.clone(), + binding: DeclarationBindingWork::default(), + manifest: SemanticBindingManifestWork::default(), + issuance: BoundDefinitionWork::default(), + failed: true, + }; + guard.accrue(QueryStructuralWork::Definition(Box::new(record.clone()))); + *attempt_record = Some(record); + self.refresh_retention_metrics(); + return Err(errors); + } }; - let first_text = program(""); - let second_text = - program("struct Noise { value: i64 }\nfn noise(value: Noise) -> i64 { value.value }\n"); - let first = well_known_option_isolation_snapshot(&first_text); - let second = well_known_option_isolation_snapshot(&second_text); - let options = CompileOptions::default(); - let runtime_symbols = |output: &crate::CanonicalSemanticOutput| { - output - .functions() - .iter() - .flat_map(|function| { - let cfg = &function.cfg; - cfg.blocks() - .iter() - .flat_map(|block| block.insts.iter()) - .filter_map(|value| match cfg.get_inst(*value).data { - rue_cfg::CfgInstData::Intrinsic { - runtime: Some(rue_air::RuntimeCallKind::ParseI64), - name, - .. - } => Some(name), - _ => None, - }) - .collect::>() - }) - .collect::>() + *execution = QueryAttemptExecution::Computed; + guard.started(); + let computation = compute_stable_definitions(&merged, options, &imports, &semantic); + let result = computation.result.clone(); + let record = DefinitionQueryRecord { + input, + binding: computation.binding, + manifest: computation.manifest, + issuance: computation.issuance, + failed: result.is_err(), }; + guard.accrue(QueryStructuralWork::Definition(Box::new(record.clone()))); + *attempt_record = Some(record); + self.refresh_retention_metrics(); + result + } +} - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &first); - session.canonical_semantic(&options).unwrap(); - publish_with_test_imports(&mut session, &second); - let warm = session.canonical_semantic(&options).unwrap(); - assert_eq!(warm.work().cfg.cfg_builds_attempted, 0); - assert!(warm.work().cfg.cfg_reuses >= 2, "{:?}", warm.work().cfg); - assert_eq!( - warm.work().cfg.optimization_attempts, - 0, - "the collector must relocate the complete optimized terminal: {:?}", - warm.work().cfg - ); - let warm_symbols = runtime_symbols(&warm); - assert_eq!(warm_symbols.len(), 1); - let warm_rir = session.canonical_rir().unwrap(); - assert_eq!( - warm_rir - .semantic_symbols() - .interner() - .resolve(&warm_symbols[0]), - "parse_i64" - ); +fn declaration_shell_failure_diagnostics( + program: &crate::canonical_merge::CanonicalMergedAst, + failure: &crate::declaration_candidate::DeclarationShellFailure, +) -> CompileErrors { + use crate::declaration_candidate::DeclarationShellFailure as F; + let key = match failure { + F::Absent(key) | F::Ambiguous(key) | F::ParserCapabilityMismatch(key) => Some(key), + F::OccurrencesUnavailable(_) => None, + }; + let span = key.and_then(|key| { + program + .modules() + .iter() + .find(|module| module.module_id() == &key.module) + .and_then(|module| module.definitions().declaration_locator(key)) + .map(|locator| locator.declaration_span) + }); + let kind = ErrorKind::InternalError(format!( + "query-owned declaration shell failed stable validation: {failure:?}" + )); + CompileErrors::from(match span { + Some(span) => CompileError::new(kind, span), + None => CompileError::without_span(kind), + }) +} - let mut fresh = CompilerSession::new(); - publish_with_test_imports(&mut fresh, &second); - let fresh_output = fresh.canonical_semantic(&options).unwrap(); - let fresh_symbols = runtime_symbols(&fresh_output); - assert_eq!(fresh_symbols.len(), 1); - let fresh_rir = fresh.canonical_rir().unwrap(); - assert_eq!( - fresh_rir - .semantic_symbols() - .interner() - .resolve(&fresh_symbols[0]), - "parse_i64" - ); - assert_eq!( - normalize_session_local_spurs(format!("{:?}", warm.functions())), - normalize_session_local_spurs(format!("{:?}", fresh_output.functions())) - ); +fn semantic_nucleus_failure_diagnostics( + program: &crate::canonical_merge::CanonicalMergedAst, + declaration: Option<&crate::declaration_candidate::DeclarationCandidateKey>, + failure: &crate::semantic_query_nucleus::SemanticNucleusFailure, +) -> CompileErrors { + use crate::semantic_query_nucleus::SemanticNucleusFailure as F; + if let (Some(declaration), F::DiagnosticAtParameter { kind, ordinal }) = (declaration, failure) + && let Some(module) = program + .modules() + .iter() + .find(|module| module.module_id() == &declaration.module) + && let Some(locator) = module.definitions().declaration_locator(declaration) + { + let parameters = module.ast().items.iter().find_map(|item| match item { + rue_parser::ast::Item::Function(function) + if function.span == locator.declaration_span => + { + Some(function.params.as_slice()) + } + rue_parser::ast::Item::Struct(structure) => structure + .methods + .iter() + .find(|method| method.span == locator.declaration_span) + .map(|method| method.params.as_slice()), + rue_parser::ast::Item::Extern(block) => block + .fns + .iter() + .find(|function| function.span == locator.declaration_span) + .map(|function| function.params.as_slice()), + _ => None, + }); + if let Some(parameter) = parameters.and_then(|parameters| parameters.get(*ordinal as usize)) + { + return CompileErrors::from(CompileError::new(kind.clone(), parameter.span)); + } } - - #[test] - fn reused_print_runtime_call_relocates_to_the_current_helper_symbol() { - let program = |prefix: &str| { - format!( - "{prefix}\ - fn probe_print() {{ println(\"literal\"); }}\n\ - fn main() -> i32 {{ probe_print(); 0 }}" - ) - }; - let runtime_calls = |output: &crate::CanonicalSemanticOutput| { - output - .functions() + if let F::DiagnosticAtDeclaration { kind, declaration } = failure + && let Some(span) = program + .modules() + .iter() + .find(|module| module.module_id() == &declaration.module) + .and_then(|module| module.definitions().declaration_locator(declaration)) + .map(|locator| locator.declaration_span) + { + return CompileErrors::from(CompileError::new(kind.clone(), span)); + } + if let (Some(declaration), F::DiagnosticAtProducerRange { kind, start, end }) = + (declaration, failure) + && let Some(producer) = program + .modules() + .iter() + .find(|module| module.module_id() == &declaration.module) + .and_then(|module| module.definitions().producer_fragment_span(declaration)) + && let (Some(start), Some(end)) = ( + producer.start.checked_add(*start), + producer.start.checked_add(*end), + ) + && start <= end + && end <= producer.end + { + return CompileErrors::from(CompileError::new( + kind.clone(), + rue_span::Span::with_file(producer.file_id, start, end), + )); + } + if let F::OwnershipGate { kind, gate } = failure { + let primary_span = declaration.and_then(|key| { + program + .modules() .iter() - .flat_map(|function| { - let cfg = &function.cfg; - cfg.blocks() - .iter() - .flat_map(|block| block.insts.iter()) - .filter_map(|value| match cfg.get_inst(*value).data { - rue_cfg::CfgInstData::Call { - runtime: Some(runtime), - name, - .. - } => Some((runtime, name)), - _ => None, - }) - .collect::>() - }) - .collect::>() + .find(|module| module.module_id() == &key.module) + .and_then(|module| module.definitions().declaration_locator(key)) + .map(|locator| locator.declaration_span) + }); + let mut error = match primary_span { + Some(span) => CompileError::new(kind.clone(), span), + None => CompileError::without_span(kind.clone()), }; - let first_text = program(""); - let second_text = program("fn noise() -> i32 { let interner_churn = 1; interner_churn }\n"); - let first = snapshot(&[(1, "/p/main.rue", "main.rue", &first_text)], 1); - let second = snapshot(&[(1, "/p/main.rue", "main.rue", &second_text)], 1); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - let cold = session.canonical_semantic(&options).unwrap(); - let cold_calls = runtime_calls(&cold); - assert_eq!(cold_calls.len(), 1); - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert_eq!(warm.work().cfg.cfg_builds_attempted, 0); - assert_eq!(warm.work().cfg.cfg_reuses, 2); - assert_eq!(warm.work().cfg.optimization_attempts, 0); - let warm_calls = runtime_calls(&warm); - assert_eq!(warm_calls.len(), 1); - assert_ne!( - cold_calls, warm_calls, - "the inserted declaration must perturb the live runtime-call symbol" - ); - let warm_rir = session.canonical_rir().unwrap(); - for (runtime, symbol) in &warm_calls { - assert_eq!( - warm_rir.semantic_symbols().interner().resolve(symbol), - runtime.helper().helper().symbol - ); + if let Some(application) = &gate.application + && let Some(span) = program + .modules() + .iter() + .find(|module| module.module_id() == &application.declaration.module) + .and_then(|module| { + module + .definitions() + .declaration_locator(&application.declaration) + }) + .map(|locator| locator.declaration_span) + { + error = error.with_label("required by the type-constructor application here", span); } - - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh_output = fresh.canonical_semantic(&options).unwrap(); - let fresh_calls = runtime_calls(&fresh_output); - assert_eq!(fresh_calls.len(), 1); - let fresh_rir = fresh.canonical_rir().unwrap(); - for (runtime, symbol) in &fresh_calls { - assert_eq!( - fresh_rir.semantic_symbols().interner().resolve(symbol), - runtime.helper().helper().symbol - ); + return CompileErrors::from(error); + } + if let (Some(declaration), F::Diagnostic(ErrorKind::CopyStructWithDestructor { type_name })) = + (declaration, failure) + && let Some(module) = program + .modules() + .iter() + .find(|module| module.module_id() == &declaration.module) + { + let destructor_span = module.ast().items.iter().find_map(|item| match item { + rue_parser::ast::Item::DropFn(drop) + if module.resolve_raw_symbol(drop.type_name.name) == type_name => + { + Some(drop.span) + } + _ => None, + }); + let copy_span = module.ast().items.iter().find_map(|item| match item { + rue_parser::ast::Item::Struct(structure) + if module.resolve_raw_symbol(structure.name.name) == type_name => + { + structure + .directives + .iter() + .find(|directive| module.resolve_raw_symbol(directive.name.name) == "copy") + .map(|directive| directive.span) + } + _ => None, + }); + if let Some(destructor_span) = destructor_span { + let mut error = CompileError::new( + ErrorKind::CopyStructWithDestructor { + type_name: type_name.clone(), + }, + destructor_span, + ) + .with_label("destructor defined here", destructor_span) + .with_note( + "`@copy` values are duplicated implicitly, so the destructor would run \ + once per copy — cleaning up the same resource multiple times", + ) + .with_help("remove the `@copy` attribute or remove the `drop fn`"); + if let Some(copy_span) = copy_span { + error = error.with_label("type declared `@copy` here", copy_span); + } + return CompileErrors::from(error); } - assert_eq!( - normalize_session_local_spurs(format!("{:?}", warm.functions())), - normalize_session_local_spurs(format!("{:?}", fresh_output.functions())) - ); } + let span = declaration.and_then(|key| { + program + .modules() + .iter() + .find(|module| module.module_id() == &key.module) + .and_then(|module| module.definitions().declaration_locator(key)) + .map(|locator| locator.declaration_span) + }); + let (kind, help) = match failure { + F::Diagnostic(kind) => (kind.clone(), None), + F::DiagnosticAtParameter { kind, .. } => (kind.clone(), None), + F::DiagnosticAtDeclaration { kind, .. } => (kind.clone(), None), + F::DiagnosticAtProducerRange { kind, .. } => (kind.clone(), None), + F::OwnershipGate { kind, .. } => (kind.clone(), None), + F::DiagnosticWithHelp { kind, help } => (kind.clone(), Some(help.clone())), + F::Cycle(nodes) => ( + ErrorKind::ConstInitializerCycle { + cycle: nodes + .iter() + .map(AsRef::as_ref) + .collect::>() + .join(" -> "), + }, + None, + ), + F::SignatureReentry { cycle, .. } => ( + ErrorKind::UnknownType( + cycle + .iter() + .map(AsRef::as_ref) + .collect::>() + .join(" -> "), + ), + None, + ), + F::Resolution(message) if message.starts_with("unknown array length") => ( + ErrorKind::InvalidArrayLength { + reason: message + .strip_prefix("unknown array length `") + .and_then(|name| name.strip_suffix('`')) + .map_or_else( + || message.to_string(), + |name| format!("'{name}' is not a compile-time constant"), + ), + }, + None, + ), + F::Resolution(message) => ( + ErrorKind::ComptimeEvaluationFailed { + reason: message.to_string(), + }, + None, + ), + F::Shell(message) | F::Syntax(message) => ( + ErrorKind::InternalError(format!("semantic query invariant failed: {message}")), + None, + ), + }; + let error = match span { + Some(span) => CompileError::new(kind, span), + None => CompileError::without_span(kind), + }; + CompileErrors::from(match help { + Some(help) => error.with_help(help.to_string()), + None => error, + }) +} - #[test] - fn opt_level_only_change_reuses_cfg_and_recomputes_optimization_per_function() { - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn left() -> i32 { 20 }\n\ - fn right() -> i32 { 22 }\n\ - fn main() -> i32 { left() + right() }", - )], - 1, - ); - let o0 = CompileOptions { - opt_level: OptLevel::O0, - ..CompileOptions::default() - }; - let o1 = CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() - }; - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - - let cold = session.canonical_semantic(&o0).unwrap(); - assert_eq!(cold.functions().len(), 3); - assert_eq!(cold.work().cfg.cfg_builds_attempted, 3); - assert_eq!(cold.work().cfg.optimization_attempts, 3); - - let optimized = session.canonical_semantic(&o1).unwrap(); - assert_eq!(optimized.functions().len(), 3); - assert_eq!(optimized.work().cfg.cfg_builds_attempted, 0); - assert_eq!(optimized.work().cfg.cfg_builds_succeeded, 0); - assert_eq!(optimized.work().cfg.cfg_builds_failed, 0); - assert_eq!(optimized.work().cfg.cfg_reuses, 3); - assert_eq!(optimized.work().cfg.cfg_import_attempts, 3); - assert_eq!(optimized.work().cfg.cfg_import_successes, 3); - assert_eq!(optimized.work().cfg.optimization_attempts, 3); - assert_eq!(optimized.work().cfg.optimization_completions, 3); - assert_eq!(optimized.work().cfg.optimized_level_attempts, 3); +fn well_known_option_resolution_diagnostics( + program: &crate::canonical_merge::CanonicalMergedAst, + failure: &crate::revisioned_query_database::WellKnownOptionResolutionFailure, +) -> CompileErrors { + use crate::revisioned_query_database::WellKnownOptionResolutionFailure as F; + match failure { + F::Incomplete { + payload, + prerequisite, + detail, + } => CompileErrors::from(CompileError::without_span(ErrorKind::InternalError( + format!( + "exact trusted Option({payload:?}) prerequisite resolution was incomplete{}: {detail}", + prerequisite + .as_ref() + .map_or_else(String::new, |key| format!(" at {key:?}")) + ), + ))), + F::Semantic { payload, failure } => { + let mut errors = semantic_nucleus_failure_diagnostics(program, None, failure); + if errors.is_empty() { + errors = CompileErrors::from(CompileError::without_span(ErrorKind::InternalError( + format!( + "trusted Option({payload:?}) resolution failed without diagnostics: {failure:?}" + ), + ))); + } + errors + } + F::WrongProjection { payload, detail } => CompileErrors::from(CompileError::without_span( + ErrorKind::InternalError(format!( + "trusted Option({payload:?}) resolution returned the wrong semantic projection: {detail}" + )), + )), } +} - #[test] - fn specialized_cfg_reuse_is_stable_across_unrelated_body_edits() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn choose(comptime n: i32) -> i32 { n }\nfn b() -> i32 { 2 }\nfn main() -> i32 { choose(40) + b() }", - )], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn choose(comptime n: i32) -> i32 { n }\nfn b() -> i32 { 3 }\nfn main() -> i32 { choose(40) + b() }", - )], - 1, - ); - let options = CompileOptions { - opt_level: OptLevel::O0, - ..CompileOptions::default() - }; - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - assert!(warm.functions().iter().any(|function| { - matches!( - function.semantic_identity, - crate::FunctionInstanceKey::Specialization { .. } - ) - })); - } +fn semantic_diagnostic_input( + input: &CodegenInputDescriptor, + imports: CanonicalImportGraph, +) -> crate::ResolvedCodegenRevision { + crate::ResolvedCodegenRevision::new( + crate::ResolvedProgramRevision::new(input.semantic.clone(), imports), + input.opt_level, + ) +} + +fn programs_are_pointer_equivalent(left: &ParsedProgram, right: &ParsedProgram) -> bool { + left.source_revision() == right.source_revision() + && left.modules().len() == right.modules().len() + && left + .modules() + .iter() + .zip(right.modules()) + .all(|(left, right)| Arc::ptr_eq(left, right)) +} - #[test] - fn cfg_reuse_rejects_target_and_callable_identity_changes() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn old() -> i32 { 1 }\nfn stable() -> i32 { 2 }\nfn main() -> i32 { old() + stable() }", - )], - 1, - ); - let renamed = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn new() -> i32 { 1 }\nfn stable() -> i32 { 2 }\nfn main() -> i32 { new() + stable() }", - )], - 1, - ); - let host = Target::host().unwrap(); - let other = if host == Target::Aarch64Linux { - Target::X86_64Linux - } else { - Target::Aarch64Linux - }; - let first_options = CompileOptions { - target: host, - opt_level: OptLevel::O1, - ..CompileOptions::default() - }; - let other_options = CompileOptions { - target: other, - opt_level: OptLevel::O1, - ..CompileOptions::default() +fn validate_accepted_read_manifest( + snapshot: &SourceSnapshot, + accepted_reads: &crate::AcceptedReadManifest, +) -> Result<(), CompileErrors> { + if accepted_reads.len() != snapshot.len() { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "accepted read manifest does not cover the staging source snapshot".into(), + ), + ))); + } + let entries = accepted_reads + .iter() + .map(|entry| (entry.module(), entry)) + .collect::>(); + if entries.len() != accepted_reads.len() { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput( + "accepted read manifest contains duplicate logical modules".into(), + ), + ))); + } + for source in snapshot.files() { + let module = snapshot + .module_id(source.file_id) + .expect("snapshot files have logical module IDs"); + let Some(entry) = entries.get(module) else { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput(format!( + "accepted read manifest is missing logical module {module}" + )), + ))); }; - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&first_options).unwrap(); - let cross_target = session.canonical_semantic(&other_options).unwrap(); - assert_eq!(cross_target.work().cfg.cfg_reuses, 0); - assert_eq!(cross_target.work().cfg.cfg_builds_attempted, 3); - session.update(&renamed).into_result().unwrap(); - let changed = session.canonical_semantic(&other_options).unwrap(); - assert_eq!(changed.work().cfg.cfg_reuses, 1); - assert_eq!(changed.work().cfg.cfg_builds_attempted, 2); - let mut fresh = CompilerSession::new(); - fresh.update(&renamed).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&other_options).unwrap(); - assert_eq!( - format!("{:?}", changed.functions()), - format!("{:?}", fresh.functions()) - ); + if entry.content_fingerprint() != crate::import_discovery::source_fingerprint(source.source) + { + return Err(CompileErrors::from(CompileError::without_span( + ErrorKind::InvalidCompilerInput(format!( + "accepted read manifest content does not match logical module {module}" + )), + ))); + } } + Ok(()) +} - #[test] - fn value_constants_install_from_the_semantic_nucleus_without_fallback() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "const n: i32 = 1; fn main() -> i32 { n }", - )], - 1, - ); - let second = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "const n: i32 = 1; fn main() -> i32 { n + 1 }", - )], - 1, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - let main = body_query_key(&mut session, &options, "main"); - let (first_stamp, _, first_transaction) = retained_body_transaction(&session, &main); - session.update(&second).into_result().unwrap(); - let output = session.canonical_semantic(&options).unwrap(); - let (second_stamp, _, second_transaction) = retained_body_transaction(&session, &main); - assert_ne!(first_stamp, second_stamp); - assert!(matches!( - first_transaction, - crate::body_query::BodyTransaction::Success { .. } - )); - assert!(matches!( - second_transaction, - crate::body_query::BodyTransaction::Success { .. } - )); - assert_eq!(output.work().binding.declaration_resolution_invocations, 0); - assert_eq!(output.work().binding.durable_install_invocations, 1); - assert_eq!(output.work().declaration_reuse.durable_records_reused, 2); - } +fn no_published_program() -> CompileErrors { + CompileErrors::from(CompileError::without_span(ErrorKind::InvalidCompilerInput( + "frontend query session has no successful parsed program".to_string(), + ))) +} - fn assert_semantic_artifact_parity( - session: &CompilerSession, - actual: &CanonicalSemanticOutput, - fresh: &CanonicalSemanticOutput, - ) { - assert_eq!( - normalize_session_local_spurs(format!("{:?}", actual.functions())), - normalize_session_local_spurs(format!("{:?}", fresh.functions())) - ); - assert_eq!(actual.strings(), fresh.strings()); - assert_eq!( - format!("{:?}", actual.warnings()), - format!("{:?}", fresh.warnings()) - ); - let diagnostics = session - .latest_diagnostics() - .expect("semantic query publishes diagnostics"); - assert!(diagnostics.is_success()); - assert_eq!( - format!("{:?}", diagnostics.warnings()), - format!("{:?}", fresh.warnings()) - ); +/// Successive fixed-point snapshots belong to one bounded discovery parse run +/// only when they preserve all prior source, manifest, context, and ledger +/// provenance. Any failed/closed/superseding attempt starts fresh accounting. +fn continues_discovery_lifecycle( + previous: &ImportDiscoveryRevisionArtifact, + snapshot: &SourceSnapshot, + context: &crate::ImportDiscoveryContext, + accepted_reads: &crate::AcceptedReadManifest, + carried_ledger: &crate::ImportObservationLedger, +) -> bool { + if previous.status != ImportDiscoveryRevisionStatus::Open || previous.context != *context { + return false; } - - fn normalize_session_local_spurs(value: String) -> String { - let mut normalized = String::with_capacity(value.len()); - let mut rest = value.as_str(); - while let Some(start) = rest.find("Spur(") { - normalized.push_str(&rest[..start]); - normalized.push_str("Spur(_)"); - let after = &rest[start + "Spur(".len()..]; - let Some(end) = after.find(')') else { - normalized.push_str(after); - return normalized; - }; - rest = &after[end + 1..]; - } - normalized.push_str(rest); - normalized + let Some(program) = previous.program.as_deref() else { + return false; + }; + if program.root() != snapshot.source_revision().root() + || !program.modules_iter().all(|module| { + let file_id = module.file_id(); + snapshot.module_id(file_id) == Some(module.module_id()) + && snapshot.source_id(file_id) == Some(module.source_id()) + && snapshot.metadata().physical_path(file_id) == Some(module.physical_path()) + }) + { + return false; + } + if !previous + .accepted_reads + .iter() + .all(|entry| accepted_reads.contains_entry(entry)) + { + return false; } + previous + .ledger + .iter() + .all(|observation| carried_ledger.get(observation.request()) == Some(observation)) +} - fn assert_body_artifact_parity( - actual: &CanonicalSemanticOutput, - fresh: &CanonicalSemanticOutput, - ) { - assert_eq!( - format!("{:?}", actual.functions()), - format!("{:?}", fresh.functions()) - ); - assert_eq!(actual.strings(), fresh.strings()); - assert_eq!( - format!("{:?}", actual.warnings()), - format!("{:?}", fresh.warnings()) - ); - assert_eq!( - format!("{:?}", actual.analyzed_body_owners()), - format!("{:?}", fresh.analyzed_body_owners()) - ); - assert_eq!(actual.type_pool().stats(), fresh.type_pool().stats()); +#[cfg(test)] +impl CompilerSession { + /// Return the producer request that owns each currently retained ordinary + /// body terminal named by `names`. A missing declaration or a declaration + /// with no retained reached-body terminal is omitted. + /// + /// The scaling harness compares these stable provenance identities across + /// revisions to prove the exact recomputed body set. Equal work counts alone + /// cannot distinguish recomputing the intended consumers from recomputing + /// the same number of unrelated bodies. + pub(crate) fn retained_body_transaction_origins_for_test( + &self, + names: &[String], + ) -> BTreeMap { + let revision = self + .queries + .revisioned + .current_semantic_revision() + .expect("the acceptance corpus has a semantic revision"); + self.queries + .revisioned + .retained_body_transaction_origins_for_test(revision, names) } - fn assert_diagnostic_parity(actual: &CompilerSession, fresh: &CompilerSession) { - let actual = actual.latest_diagnostics().unwrap(); - let fresh = fresh.latest_diagnostics().unwrap(); - assert_eq!( - format!("{:?}", actual.stage()), - format!("{:?}", fresh.stage()) - ); - assert_eq!( - format!("{:?}", actual.errors()), - format!("{:?}", fresh.errors()) - ); - assert_eq!( - format!("{:?}", actual.warnings()), - format!("{:?}", fresh.warnings()) - ); + /// Snapshot every retained body identity and its current observable + /// transaction for the correctness oracle. The map includes stale cache + /// identities with `None` when invalidation has made their terminal + /// unobservable at the current revision. + #[allow(dead_code)] + pub(crate) fn retained_body_identity_states_for_test( + &self, + options: &CompileOptions, + ) -> BTreeMap> { + let Some(revision) = self.queries.revisioned.current_semantic_revision() else { + return BTreeMap::new(); + }; + self.queries + .revisioned + .retained_body_identity_states_for_test( + revision, + crate::semantic_query_nucleus::SemanticQueryConfiguration { + target: options.target, + preview_features: StablePreviewFeatures::new(&options.preview_features), + }, + ) } +} - #[test] - fn generic_callable_signature_round_trips_through_durable_declaration_reuse() { - let source = |value| { - snapshot( +#[cfg(test)] +mod tests { + fn evict_diagnostic_index(session: &mut CompilerSession) { + for revision in 0..=FRONTEND_DIAGNOSTIC_RETENTION_LIMIT { + let source = snapshot( &[( - 1, - "/p/main.rue", + 91, + "/eviction/main.rue", "main.rue", - &format!( - "fn id(comptime T: type, value: T) -> T {{ value }} fn main() -> i32 {{ {value} }}" - ), + &format!("fn main() -> i32 {{ {revision} }}"), )], - 1, - ) - }; - let first = source(1); - let edited = source(2); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - - session.update(&edited).into_result().unwrap(); - let reused = session.canonical_semantic(&options).unwrap(); - assert_eq!(reused.work().binding.declaration_resolution_invocations, 0); - assert_eq!(reused.work().binding.durable_payloads_installed, 2); - assert_eq!(reused.work().declaration_reuse.durable_records_reused, 2); - assert_eq!( - reused.work().declaration_reuse.declaration_prefix_fallbacks, - 0 - ); - - let mut fresh = CompilerSession::new(); - fresh.update(&edited).into_result().unwrap(); - let ordinary = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &reused, &ordinary); - assert_diagnostic_parity(&session, &fresh); + 91, + ); + let snapshot = Arc::new(FrontendDiagnosticSnapshot { + source, + stage: FrontendDiagnosticIdentity::Syntax, + provenance: DiagnosticAttemptProvenance::Canonical, + errors: Arc::from([]), + warnings: Arc::from([]), + }); + session.diagnostics.select_test_snapshot(snapshot); + } } - #[test] - fn composite_generic_signature_reuses_across_relocation_and_specialization_edit() { - let source = |file, physical: &str, value| { - snapshot( - &[( - file, - physical, - "main.rue", - &format!( - "fn first(comptime T: type, values: [[T; 2]; 2]) -> T {{ values[0][0] }} fn main() -> i32 {{ first(i32, [[1, 2], [3, {value}]]) }}" - ), - )], - file, - ) - }; - let first = source(1, "/old/main.rue", 4); - let relocated_edit = source(99, "/new/main.rue", 5); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); + /// Publish `source` and, when it imports, commit its graph through a real + /// discovery epoch served from the fixture's own modules. The returned work + /// is the epoch's accumulated parse work, which is the parse an + /// import-bearing revision actually performs. - session.update(&relocated_edit).into_result().unwrap(); - let reused = session.canonical_semantic(&options).unwrap(); - assert_eq!(reused.work().binding.declaration_resolution_invocations, 0); - assert_eq!(reused.work().binding.durable_payloads_installed, 2); - assert_eq!(reused.work().declaration_reuse.durable_records_reused, 2); - assert_eq!( - reused.work().declaration_reuse.declaration_prefix_fallbacks, - 0 - ); + fn publish_with_test_imports( + session: &mut CompilerSession, + source: &SourceSnapshot, + ) -> ParsedModulesWork { + if !crate::test_support::fixture_has_imports(source).unwrap() { + let update = session.update(source); + let work = update.work(); + update.into_owner_result().unwrap(); + return work; + } + crate::test_support::TestDiscoveryHost::new(source) + .unwrap() + .drive(session) + .unwrap() + .parse_work + } - let mut fresh = CompilerSession::new(); - fresh.update(&relocated_edit).into_result().unwrap(); - let ordinary = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &reused, &ordinary); - assert_diagnostic_parity(&session, &fresh); + fn body_query_key( + session: &mut CompilerSession, + options: &CompileOptions, + name: &str, + ) -> crate::body_query::BodyQueryKey { + let definitions = session.stable_definitions(options).unwrap(); + let definition = definitions + .definitions() + .iter() + .find(|record| { + record.stable_key().kind() == StableDefinitionKind::Function + && record.stable_key().name() == name + }) + .unwrap() + .stable_key() + .clone(); + crate::body_query::BodyQueryKey { + instance: crate::FunctionInstanceKey::Definition(definition), + configuration: crate::semantic_query_nucleus::SemanticQueryConfiguration { + target: options.target, + preview_features: StablePreviewFeatures::new(&options.preview_features), + }, + } } - #[test] - fn semantic_nucleus_installs_nested_generic_signatures_without_fallback() { - let source = |value| { - snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - &format!( - "fn first(comptime T: type, values: [[T; 2]; 2]) -> T {{ values[0][0] }} fn main() -> i32 {{ first(i32, [[1, 2], [3, {value}]]) }}" - ), - )], - 1, - ) - }; - let first = source(4); - let edited = source(5); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); + fn retained_body_query_stamps( + session: &CompilerSession, + key: &crate::body_query::BodyQueryKey, + ) -> (u64, u64, u64, u64) { + let revision = session + .queries + .revisioned + .current_semantic_revision() + .unwrap(); + let cancellation = rue_query::CancellationToken::new(); + let transaction = session + .queries + .revisioned + .body_transaction(revision, key.clone(), cancellation.clone()) + .unwrap(); + let body = session + .queries + .revisioned + .canonical_body_projection(revision, key.clone(), cancellation.clone()) + .unwrap(); + let references = session + .queries + .revisioned + .body_references_projection(revision, key.clone(), cancellation.clone()) + .unwrap(); + let produced_anonymous = session + .queries + .revisioned + .body_produced_anonymous_projection(revision, key.clone(), cancellation) + .unwrap(); + ( + transaction.stamp(), + body.stamp(), + references.stamp(), + produced_anonymous.stamp(), + ) + } - session.update(&edited).into_result().unwrap(); - let actual = session.canonical_semantic(&options).unwrap(); - assert_eq!(actual.work().binding.declaration_resolution_invocations, 0); - assert_eq!(actual.work().declaration_reuse.install_invocations, 1); - assert_eq!(actual.work().declaration_reuse.durable_records_reused, 2); - assert_eq!(actual.work().declaration_reuse.fallbacks, 0); - assert_eq!( - actual.work().declaration_reuse.declaration_prefix_fallbacks, - 0 - ); - assert_eq!( - actual.work().declaration_reuse.declaration_prefixes_built, - 1 - ); + fn retained_body_transaction( + session: &CompilerSession, + key: &crate::body_query::BodyQueryKey, + ) -> ( + u64, + rue_query::QueryTerminalKind, + crate::body_query::BodyTransaction, + ) { + let revision = session + .queries + .revisioned + .current_semantic_revision() + .unwrap(); + let terminal = session + .queries + .revisioned + .body_transaction(revision, key.clone(), rue_query::CancellationToken::new()) + .unwrap(); + let rue_query::QueryOutcome::Success(transaction) = terminal.outcome() else { + unreachable!("BodyTransaction publishes typed values") + }; + (terminal.stamp(), terminal.kind(), transaction.clone()) + } - let mut fresh = CompilerSession::new(); - fresh.update(&edited).into_result().unwrap(); - let ordinary = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &actual, &ordinary); - assert_eq!(actual.type_pool().stats(), ordinary.type_pool().stats()); - assert_diagnostic_parity(&session, &fresh); + fn retained_body_dependency_nodes( + session: &CompilerSession, + key: &crate::body_query::BodyQueryKey, + ) -> Vec { + let revision = session + .queries + .revisioned + .current_semantic_revision() + .unwrap(); + session + .queries + .revisioned + .body_transaction(revision, key.clone(), rue_query::CancellationToken::new()) + .unwrap() + .dependencies() + .iter() + .map(|dependency| format!("{:?}", dependency.node)) + .collect() } - #[test] - fn comptime_named_method_reuses_declarations_while_body_reuse_fails_closed() { - let source = |body: &str| snapshot(&[(1, "/p/main.rue", "main.rue", body)], 1); - let first = source( - "struct Value { fn choose(borrow self, comptime n: i32) -> i32 { n } } fn main() -> i32 { let value = Value {}; value.choose(1) }", - ); - let edited = source( - "struct Value { fn choose(borrow self, comptime n: i32) -> i32 { n + 1 } } fn main() -> i32 { let value = Value {}; value.choose(1) }", - ); - let supported = source("fn main() -> i32 { 1 }"); - let supported_edit = source("fn main() -> i32 { 2 }"); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - let cold = session.canonical_semantic(&options).unwrap(); - assert_eq!(cold.work().binding.declaration_resolution_invocations, 0); - assert_eq!(cold.work().binding.durable_install_invocations, 1); + /// A trusted-std `Option` snapshot for the well-known query-edge isolation + /// regression: the root is `root_source`, and the trusted std `Option` module + /// is provided at `\0rue-std/option.rue`, reached with + /// `@import("std/option.rue")` (physical-suffix match). - session.update(&edited).into_result().unwrap(); - let ordinary = session.canonical_semantic(&options).unwrap(); - assert_eq!( - ordinary.work().binding.declaration_resolution_invocations, - 0 - ); - assert_eq!(ordinary.work().binding.durable_install_invocations, 1); - assert_eq!(ordinary.work().binding.durable_payloads_installed, 3); - assert_eq!(ordinary.work().declaration_reuse.durable_records_reused, 3); - assert_eq!( - ordinary - .work() - .declaration_reuse - .declaration_prefix_fallbacks, - 0 - ); - let mut fresh = CompilerSession::new(); - fresh.update(&edited).into_result().unwrap(); - let expected = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &ordinary, &expected); + fn well_known_option_isolation_snapshot(root_source: &str) -> SourceSnapshot { + well_known_option_snapshot_with_source( + root_source, + "pub fn Option(comptime T: type) -> type { enum { Some(T), None } }", + ) + } - // Moving to a different declaration universe seeds a new baseline, and - // its next body edit can reuse normally. - session.update(&supported).into_result().unwrap(); - let seeded = session.canonical_semantic(&options).unwrap(); - assert_eq!(seeded.work().binding.declaration_resolution_invocations, 0); - assert_eq!(seeded.work().binding.durable_install_invocations, 1); - session.update(&supported_edit).into_result().unwrap(); - let recovered = session.canonical_semantic(&options).unwrap(); - assert_eq!( - recovered.work().binding.declaration_resolution_invocations, - 0 - ); - assert_eq!(recovered.work().binding.durable_payloads_installed, 1); + fn well_known_option_snapshot_with_source( + root_source: &str, + option_source: &str, + ) -> SourceSnapshot { + let root = FileId::new(1); + let option = FileId::new(2); + let metadata = SourceMetadata::new_with_trusted_standard_library( + root, + HashMap::from([ + (root, "/project/main.rue".to_owned()), + (option, "/project/std/option.rue".to_owned()), + ]), + HashMap::from([ + (root, "main.rue".to_owned()), + (option, "\0rue-std/option.rue".to_owned()), + ]), + HashSet::from([option]), + ) + .unwrap(); + SourceSnapshot::new( + metadata, + vec![ + (root, Arc::new(root_source.to_owned())), + (option, Arc::new(option_source.to_owned())), + ], + ) + .unwrap() } - #[test] - fn anonymous_structural_body_operations_export_durably_after_declaration_reuse() { - let first = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn Box(comptime T: type) -> type { struct { value: T, fn get(borrow self) -> T { self.value } } } fn main() -> i32 { let B = Box(i32); let value = B { value: 1 }; value.get() }", - )], - 1, - ); - let edited = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn Box(comptime T: type) -> type { struct { value: T, fn get(borrow self) -> T { self.value } } } fn main() -> i32 { let B = Box(i32); let value = B { value: 2 }; value.get() }", - )], - 1, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - let cold = session.canonical_semantic(&options).unwrap(); - assert_eq!(cold.work().binding.declaration_resolution_invocations, 0); - assert_eq!(cold.work().binding.durable_install_invocations, 1); + use std::{ + collections::{HashMap, HashSet}, + sync::Arc, + }; - session.update(&edited).into_result().unwrap(); - let ordinary = session.canonical_semantic(&options).unwrap(); - assert_eq!( - ordinary.work().binding.declaration_resolution_invocations, - 0 - ); - assert_eq!(ordinary.work().binding.durable_install_invocations, 1); - assert_eq!(ordinary.work().binding.durable_payloads_installed, 2); - assert_eq!(ordinary.work().declaration_reuse.durable_records_reused, 2); - assert_eq!( - ordinary - .work() - .declaration_reuse - .declaration_prefix_fallbacks, - 0 - ); - // Type producers are query inputs, not runtime function bodies. The - // reached executable set is `main` plus the anonymous `get` method. - assert_eq!(ordinary.functions().len(), 2); - let mut fresh = CompilerSession::new(); - fresh.update(&edited).into_result().unwrap(); - let expected = fresh.canonical_semantic(&options).unwrap(); - assert_semantic_artifact_parity(&session, &ordinary, &expected); + use rue_span::FileId; - let supported = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }")], - 1, - ); - let supported_edit = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 2 }")], - 1, - ); - session.update(&supported).into_result().unwrap(); - let seeded = session.canonical_semantic(&options).unwrap(); - assert_eq!(seeded.work().binding.declaration_resolution_invocations, 0); - assert_eq!(seeded.work().binding.durable_install_invocations, 1); - session.update(&supported_edit).into_result().unwrap(); - let recovered = session.canonical_semantic(&options).unwrap(); - assert_eq!( - recovered.work().binding.declaration_resolution_invocations, - 0 - ); - assert_eq!(recovered.work().binding.durable_payloads_installed, 1); - } + use super::*; + use crate::{ + ModuleId, OptLevel, PreviewFeature, PreviewFeatures, SourceMetadata, SourceSnapshot, + StableDefinitionKey, StableDefinitionKind, Target, + }; #[test] - fn signature_target_and_failed_body_changes_fail_closed_and_recovery_reuses() { - let base = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn value() -> i32 { 1 } fn main() { value(); }", - )], - 1, - ); - let signature = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn value() -> i64 { 1 } fn main() { value(); }", - )], - 1, - ); - let broken_body = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn value() -> i64 { missing } fn main() { value(); }", - )], - 1, - ); - let recovered = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn value() -> i64 { 2 } fn main() { value(); }", - )], - 1, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&base).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - - session.update(&signature).into_result().unwrap(); - let changed = session.canonical_semantic(&options).unwrap(); - assert_eq!(changed.work().binding.declaration_resolution_invocations, 0); - - session.update(&broken_body).into_result().unwrap(); - assert!(session.canonical_semantic(&options).is_err()); - session.update(&recovered).into_result().unwrap(); - let recovered = session.canonical_semantic(&options).unwrap(); + fn phase_three_module_queries_close_the_exact_import_deletion_gate() { + let production = include_str!("session.rs") + .split("\n#[cfg(test)]\nmod tests {") + .next() + .unwrap(); + assert!(!production.contains("RUE-1024 DELETION GATE")); + let discovery = include_str!("import_discovery.rs"); + assert!(!discovery.contains("pub fn pending_requests(")); + let revisioned = include_str!("revisioned_query_database.rs"); + for family in [ + "compiler.parse-module", + "compiler.module-index", + "compiler.lookup-name", + "compiler.resolve-import", + "compiler.module-rir", + ] { + assert!( + revisioned.contains(family), + "missing canonical family {family}" + ); + } + assert!(!revisioned.contains("ImportModuleDemand")); + assert!(!revisioned.contains("compiler.import-module-frontier")); assert_eq!( - recovered.work().binding.declaration_resolution_invocations, + revisioned + .matches("RUE-1026 DELETION GATE: this selected-revision compatibility") + .count(), 0 ); - assert_eq!(recovered.work().declaration_reuse.durable_records_reused, 2); - - let mut other_target = options.clone(); - other_target.target = *Target::all() - .iter() - .find(|target| **target != options.target) - .unwrap(); - let target_changed = session.canonical_semantic(&other_target).unwrap(); + let unstable = include_str!("unstable.rs"); assert_eq!( - target_changed - .work() - .binding - .declaration_resolution_invocations, + unstable + .matches("Full-plan host compatibility adapter. RUE-1026") + .count(), 0 ); } #[test] - fn repeated_queries_and_noop_update_retain_pointer_identity() { - fn assert_send_sync() {} - assert_send_sync::(); - assert_send_sync::(); - assert_send_sync::(); - - let source = base(); - let mut session = CompilerSession::new(); - let first_program = session.update(&source).into_owner_result().unwrap(); - let first_merge = session.merge().unwrap(); - let second_merge = session.merge().unwrap(); - let first_rir = session.canonical_rir().unwrap(); - let second_rir = session.canonical_rir().unwrap(); - assert!(Arc::ptr_eq(&first_merge, &second_merge)); - assert!(Arc::ptr_eq(&first_rir, &second_rir)); - - let noop = session.update(&source); - assert!(!noop.downstream_invalidated()); - let second_program = noop.into_owner_result().unwrap(); - assert!(Arc::ptr_eq(&first_program, &second_program)); - assert!(Arc::ptr_eq(&first_merge, &session.merge().unwrap())); - assert!(Arc::ptr_eq(&first_rir, &session.canonical_rir().unwrap())); - assert_eq!(session.work().merge.executions, 1); - assert_eq!(session.work().rir.executions, 1); - assert_eq!(session.work().downstream_invalidations, 0); - - let published = session.published_owner().unwrap().clone(); - let merged = first_merge.clone(); - let rir = first_rir.clone(); - std::thread::spawn(move || { - assert_eq!(published.modules().len(), 2); - assert_eq!(merged.ast().modules().len(), 2); - assert!(!rir.rir().is_empty()); - }) - .join() - .unwrap(); - } + fn import_discovery_has_no_public_bypass_authority() { + let discovery = include_str!("import_discovery.rs") + .split("\n#[cfg(test)]\nmod tests {") + .next() + .unwrap(); + let session = include_str!("session.rs") + .split("\n#[cfg(test)]\nmod tests {") + .next() + .unwrap(); + assert!(!discovery.contains("RUE-1033 DELETION/REPLACEMENT GATE")); + for declaration in [ + "pub fn import_discovery_plan(", + "pub fn stage_import_discovery(", + "pub fn close_import_discovery(", + ] { + assert!( + !session.contains(declaration), + "public import-discovery bypass returned: {declaration}" + ); + } - #[test] - fn one_edit_among_128_recomputes_downstream_once() { - let make = |edited: bool| { - let physical = (0..128) - .map(|index| (FileId::new(index), format!("/p/m{index}.rue"))) - .collect(); - let logical = (0..128) - .map(|index| (FileId::new(index), format!("m{index}.rue"))) - .collect(); - let metadata = SourceMetadata::new(FileId::new(0), physical, logical).unwrap(); - SourceSnapshot::new( - metadata, - (0..128) - .map(|index| { - let value = if edited && index == 81 { 2 } else { 1 }; - ( - FileId::new(index), - Arc::new(format!("fn f{index}() -> i32 {{ {value} }}")), - ) - }) - .collect(), - ) + let unstable = include_str!("unstable.rs"); + let begin = unstable + .split_once("pub fn begin_import_input_request(") .unwrap() - }; - let mut session = CompilerSession::new(); - session.update(&make(false)).into_result().unwrap(); - session.canonical_rir().unwrap(); - let first_shards = session - .definition_shard_baseline - .as_ref() + .1 + .split_once(") -> crate::CompileResult") .unwrap() - .shards() - .to_vec(); - let update = session.update(&make(true)); - assert!(update.downstream_invalidated()); - assert_eq!(update.work().modules_reused, 127); - assert_eq!(update.work().modules_reparsed, 1); - session.canonical_rir().unwrap(); - let second_shards = session.definition_shard_baseline.as_ref().unwrap().shards(); - assert!( - first_shards + .0; + assert!(!begin.contains("ImportObservationLedger")); + assert!(!begin.contains("carried_ledger")); + for boundary in [ + "pub fn stage_import_input_request(", + "pub fn close_import_input_request(", + ] { + assert!( + unstable.contains(boundary), + "canonical import boundary is missing: {boundary}" + ); + } + } + + fn snapshot(entries: &[(u32, &str, &str, &str)], root: u32) -> SourceSnapshot { + let physical = entries + .iter() + .map(|(id, path, _, _)| (FileId::new(*id), (*path).to_owned())) + .collect::>(); + let logical = entries + .iter() + .map(|(id, _, logical, _)| (FileId::new(*id), (*logical).to_owned())) + .collect::>(); + let metadata = SourceMetadata::new(FileId::new(root), physical, logical).unwrap(); + SourceSnapshot::new( + metadata, + entries .iter() - .zip(second_shards) - .all(|(first, second)| Arc::ptr_eq(first, second)) - ); - assert_eq!(session.work().last_merge.definition_shards_indexed, 128); - assert_eq!(session.work().last_merge.definition_shards_reused, 128); - assert_eq!(session.work().last_merge.definition_shards_rebuilt, 0); - session.canonical_rir().unwrap(); - assert_eq!(session.work().merge.executions, 2); - assert_eq!(session.work().rir.executions, 2); - assert_eq!(session.work().downstream_invalidations, 1); + .map(|(id, _, _, text)| (FileId::new(*id), Arc::new((*text).to_owned()))) + .collect(), + ) + .unwrap() } - #[test] - fn definition_shards_fail_closed_on_surface_identity_changes() { - let initial = snapshot( - &[ - (1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/p/a.rue", "a.rue", "fn a() -> i32 { 1 }"), - ], - 1, - ); - let body = snapshot( - &[ - (1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/p/a.rue", "a.rue", "fn a() -> i32 { 2 }"), - ], - 1, - ); - let renamed_definition = snapshot( - &[ - (1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/p/a.rue", "a.rue", "fn b() -> i32 { 2 }"), - ], - 1, - ); - let relocated = snapshot( + fn base() -> SourceSnapshot { + snapshot( &[ - (1, "/m/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/m/a.rue", "a.rue", "fn b() -> i32 { 2 }"), + (7, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }"), + (2, "/p/a.rue", "a.rue", "fn a() {}"), ], + 7, + ) + } + + #[test] + fn absent_trusted_option_parks_the_rooted_attempt_with_exact_demand_and_anchor() { + // RUE-1112: a freestanding program whose reached `main` body uses a + // fallible intrinsic while NO trusted std module is present. The + // rooted attempt must park with exactly the `option.rue` demand, anchored + // on the demanding body (`main`), and must NOT run or publish any body + // transaction — the unsatisfied prerequisite stops the worklist before it + // enters `body_transaction`. + let source = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }", + )], 1, ); - let reassigned = snapshot( - &[ - (11, "/m/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (12, "/m/a.rue", "a.rue", "fn b() -> i32 { 2 }"), - ], - 11, - ); let mut session = CompilerSession::new(); - session.update(&initial).into_result().unwrap(); - session.merge().unwrap(); - - session.update(&body).into_result().unwrap(); - session.merge().unwrap(); - assert_eq!(session.work().last_merge.definition_shards_reused, 2); - assert_eq!(session.work().last_merge.definition_shards_rebuilt, 0); + session.update(&source).into_result().unwrap(); - session.update(&renamed_definition).into_result().unwrap(); - session.merge().unwrap(); - assert_eq!(session.work().last_merge.definition_shards_reused, 1); - assert_eq!(session.work().last_merge.definition_shards_rebuilt, 1); + let park = match session.semantic_or_toolchain_park(&CompileOptions::default()) { + SemanticParkOutcome::Parked(park) => park, + SemanticParkOutcome::Ready(_) => { + panic!("expected a trusted-toolchain park, got successful analysis") + } + SemanticParkOutcome::Errors(errors) => { + panic!("expected a trusted-toolchain park, got errors: {errors:?}") + } + }; - session.update(&relocated).into_result().unwrap(); - session.merge().unwrap(); - assert_eq!(session.work().last_merge.definition_shards_reused, 2); - assert_eq!(session.work().last_merge.definition_shards_rebuilt, 0); + // Exact demand set: exactly the trusted std `Option` module. + let demands: Vec<&str> = park + .demands() + .iter() + .map(crate::TrustedToolchainModuleDemand::logical_path) + .collect(); + assert_eq!(demands, vec![crate::OPTION_MODULE_LOGICAL_PATH]); - session.update(&reassigned).into_result().unwrap(); - session.merge().unwrap(); - assert_eq!(session.work().last_merge.definition_shards_reused, 0); - assert_eq!(session.work().last_merge.definition_shards_rebuilt, 2); - } + // Exact requester anchor: the demanding body's stable key (`main`). + assert_eq!(park.requesters().len(), 1); + let anchor = &park.requesters()[0]; + assert_eq!(anchor.name(), "main"); + assert_eq!(anchor.kind(), crate::StableDefinitionKind::Function); - #[test] - fn syntax_failure_preserves_published_revision_and_cached_queries() { - let source = base(); - let broken = snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main( {"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, + // No body transaction ran or published a terminal. + assert!( + !session.queries.revisioned.any_body_transaction_terminal(), + "the park must precede any body transaction", ); - let mut session = CompilerSession::new(); - let program = session.update(&source).into_owner_result().unwrap(); - let merged = session.merge().unwrap(); - let rir = session.canonical_rir().unwrap(); - let failed = session.update(&broken); - assert!(failed.result().is_err()); - assert!(!failed.downstream_invalidated()); - assert!(Arc::ptr_eq(session.published_owner().unwrap(), &program)); - assert!(Arc::ptr_eq(&session.merge().unwrap(), &merged)); - assert!(Arc::ptr_eq(&session.canonical_rir().unwrap(), &rir)); } #[test] - fn duplicate_merge_error_is_memoized_and_recovery_invalidates_it() { - let duplicate = snapshot( + fn already_reached_parks_batch_into_one_park_with_unioned_demands_and_anchors() { + // RUE-1112 C2: two reached helper bodies demand different trusted modules + // (a: parse -> Option; b: read_line -> Option+StrBuf) while no trusted std + // is present. `main` reaches both, then the first to park must batch the + // remaining already-reached body: ONE park carrying the UNION of absent + // modules ([Option, StrBuf]) and BOTH requester anchors, so a single + // successor acquisition satisfies everything. + let source = snapshot( &[( 1, "/p/main.rue", "main.rue", - "fn same() {} fn same() {} fn main() {}", + "fn a() -> i32 { let _ = @parse_i64(\"1\"); 0 }\n\ + fn b() -> i32 { let _ = @read_line(); 0 }\n\ + fn main() -> i32 { a() + b() }", )], 1, ); - let fixed = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, + let mut session = CompilerSession::new(); + session.update(&source).into_result().unwrap(); + + let park = match session.semantic_or_toolchain_park(&CompileOptions::default()) { + SemanticParkOutcome::Parked(park) => park, + SemanticParkOutcome::Ready(_) => panic!("expected a batched park, got ready analysis"), + SemanticParkOutcome::Errors(errors) => { + panic!("expected a batched park, got errors: {errors:?}") + } + }; + + // Union of absent modules across both already-reached bodies, sorted. + let demands: Vec<&str> = park + .demands() + .iter() + .map(crate::TrustedToolchainModuleDemand::logical_path) + .collect(); + assert_eq!( + demands, + vec![ + crate::OPTION_MODULE_LOGICAL_PATH, + crate::STRBUF_MODULE_LOGICAL_PATH + ] ); + + // Both demanding bodies contribute a requester anchor. That both `a` and + // `b` appear proves neither transacted before the park — each was still + // pending and got projected into the one batch (`main`, which has no + // fallible intrinsic, does run its transaction first, as expected). + let anchors: std::collections::BTreeSet<&str> = + park.requesters().iter().map(|key| key.name()).collect(); + assert_eq!(anchors, std::collections::BTreeSet::from(["a", "b"])); + } + + // ---- RUE-1112: trusted-toolchain continuation + successor publication ---- + + fn continuation_std_context() -> crate::ImportDiscoveryContext { + crate::ImportDiscoveryContext::new(1, "/project", Some("/sdk"), "test-policy").unwrap() + } + + fn continuation_metadata() -> crate::FileMetadataFingerprint { + crate::FileMetadataFingerprint::new(10, 20, 30) + } + + /// Drive `root_source` to a canonical import-discovery close, then run the + /// rooted semantic attempt so its park atomically attaches the demanded-missing + /// set to the closed continuation. Returns the session (now holding an + /// AUTHORIZING continuation), its token, the empty closure-witness frontier, the + /// predecessor snapshot, its accepted reads, and the assembler ready to add + /// trusted leaves. Panics unless the attempt parked — the caller supplies a + /// reached-fallible-intrinsic root whose demand set is the acquisition batch. + /// + /// This exercises the real protocol (close → park → attach → mint): demand + /// authority is never seeded by direct field assignment, so a close whose + /// attempt never parks yields no token. + fn closed_continuation_for( + root_source: &str, + ) -> ( + CompilerSession, + ClosedDiscoveryContinuation, + crate::ImportDemandFrontier, + SourceSnapshot, + crate::AcceptedReadManifest, + crate::DiscoverySourceAssembler, + ) { + let ctx = continuation_std_context(); + let mut assembler = crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + Arc::new(root_source.to_owned()), + ) + .unwrap(); + let snapshot = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); let mut session = CompilerSession::new(); - session.update(&duplicate).into_result().unwrap(); - let first = session.merge().unwrap_err(); - let second = session.merge().unwrap_err(); - assert_eq!(format!("{first:?}"), format!("{second:?}")); - assert!(session.canonical_rir().is_err()); + let revision = session + .begin_import_input_request(&snapshot, ctx.clone(), reads.clone()) + .unwrap(); + let plan = session + .stage_import_discovery( + &snapshot, + ctx.clone(), + reads.shared_slice(), + crate::ImportObservationLedger::default(), + ) + .unwrap(); + let roots = plan.demand_roots(); + let frontier = session + .import_demand_frontier_for_roots( + revision, + &plan, + crate::ImportDemandMode::Rooted, + &roots, + ) + .unwrap(); assert!( - session - .canonical_semantic(&CompileOptions::default()) - .is_err() - ); - assert_eq!(session.work().merge.executions, 1); - assert_eq!(session.work().rir.executions, 0); - assert_eq!(session.work().semantic.executions, 0); - let merge_attempts = session - .metrics - .attempts() - .into_iter() - .filter(|attempt| attempt.family == "merge") - .collect::>(); - // The failed RIR terminal observes the failed merge stamp, so the - // semantic rejection reuses that deterministic failure without a - // fourth merge request. - assert_eq!(merge_attempts.len(), 3); - assert_eq!( - merge_attempts[0].attempt.execution(), - QueryAttemptExecution::Computed - ); - assert_eq!( - merge_attempts[0].attempt.outcome(), - crate::typed_query_store::AttemptOutcomeKind::Failure - ); - let QueryStructuralWork::Merge(failed_work) = merge_attempts[0].attempt.work() else { - panic!("failed merge must retain typed structural work"); - }; - assert_eq!(failed_work.modules_visited, 1); - assert_eq!(failed_work.items_visited, 3); - assert_eq!( - merge_attempts[1].attempt.execution(), - QueryAttemptExecution::Reused + frontier.requests().is_empty(), + "a freestanding root closes with an empty frontier", ); - assert_eq!( - merge_attempts[1].attempt.origin_id(), - merge_attempts[0].attempt.id() + let ledger = session.import_observation_ledger(revision).unwrap(); + session.close_import_discovery(ledger).unwrap(); + // A bare close is non-authorizing: no demand set has been attached yet. + assert!( + session.closed_discovery_continuation().is_none(), + "a close mints no token until a rooted park attaches a demanded set", ); - assert_eq!(merge_attempts[1].attempt.work(), &QueryStructuralWork::None); - assert!(merge_attempts[1..].iter().all(|attempt| { - attempt.attempt.execution() == QueryAttemptExecution::Reused - && attempt.attempt.outcome() - == crate::typed_query_store::AttemptOutcomeKind::Failure - && attempt.attempt.origin_id() == merge_attempts[0].attempt.id() - && attempt.attempt.work() == &QueryStructuralWork::None - })); - - let update = session.update(&fixed); - assert!(update.downstream_invalidated()); - update.into_result().unwrap(); - assert!(session.canonical_rir().is_ok()); - assert_eq!(session.work().merge.executions, 2); - assert_eq!(session.work().rir.executions, 1); + // The rooted attempt parks; the park attaches its exact demanded-missing + // set to this closed state, making the continuation authorizing. + match session.semantic_or_toolchain_park(&CompileOptions::default()) { + SemanticParkOutcome::Parked(_) => {} + SemanticParkOutcome::Ready(_) => { + panic!("expected the reached fallible intrinsic to park the rooted attempt") + } + SemanticParkOutcome::Errors(errors) => { + panic!("expected a trusted-toolchain park, got errors: {errors:?}") + } + } + let token = session + .closed_discovery_continuation() + .expect("an attached rooted park makes the closed continuation authorizing"); + (session, token, frontier, snapshot, reads, assembler) } - #[test] - fn failed_merge_attempt_identity_includes_presentation_order() { - let forward = snapshot( - &[ - ( - 1, - "/p/main.rue", - "main.rue", - "fn same() {} fn same() {} fn main() {}", - ), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 1, - ); - let reversed = snapshot( - &[ - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ( - 1, - "/p/main.rue", - "main.rue", - "fn same() {} fn same() {} fn main() {}", - ), - ], - 1, - ); - assert_eq!(forward.source_revision(), reversed.source_revision()); - let mut session = CompilerSession::new(); - session - .update_for_presentation(&forward) - .into_result() - .unwrap(); - session.merge().unwrap_err(); - session - .update_for_presentation(&reversed) - .into_result() - .unwrap(); - session.merge().unwrap_err(); - - let identities = session - .queries - .merge - .attempt_history() - .filter(|attempt| attempt.execution == QueryAttemptExecution::Computed) - .map(|attempt| { - ( - attempt.key.source.revision.clone(), - attempt.key.presentation.clone().unwrap(), - ) - }) - .collect::>(); - assert_eq!(identities.len(), 2); - assert_eq!(identities[0].0, identities[1].0); - assert_ne!(identities[0].1, identities[1].1); - assert_eq!(session.work().merge.executions, 2); + /// The common single-module case: a reached `@parse_i64` parks on exactly the + /// trusted std `Option` module. + fn closed_continuation() -> ( + CompilerSession, + ClosedDiscoveryContinuation, + crate::ImportDemandFrontier, + SourceSnapshot, + crate::AcceptedReadManifest, + crate::DiscoverySourceAssembler, + ) { + closed_continuation_for("fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }") } - #[test] - fn switching_update_modes_is_a_distinct_diagnostic_identity() { - // RUE-775: canonical `update()` and `update_for_presentation()` over - // one byte-identical snapshot must not reuse each other's merge - // diagnostics — the presentation provenance is part of the attempt - // key — while returning to an already-computed mode reuses it. - let source = snapshot( - &[ - ( - 1, - "/p/main.rue", - "main.rue", - "fn same() {} fn same() {} fn main() {}", + fn add_trusted_option(assembler: &mut crate::DiscoverySourceAssembler) { + assembler + .add_explicit( + "/sdk/option.rue", + "/sdk/option.rue", + crate::PhysicalFileIdentity::new(2, 2), + continuation_metadata(), + Arc::new( + "pub fn Option(comptime T: type) -> type { enum { Some(T), None } }".to_owned(), ), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session.merge().unwrap_err(); - session - .update_for_presentation(&source) - .into_result() + ) .unwrap(); - session.merge().unwrap_err(); - assert_eq!(session.work().merge.executions, 2); - - let identities = session - .queries - .merge - .attempt_history() - .filter(|attempt| attempt.execution == QueryAttemptExecution::Computed) - .map(|attempt| { - ( - attempt.key.source.revision.clone(), - attempt.key.presentation.clone(), - ) - }) - .collect::>(); - assert_eq!(identities.len(), 2); - assert_eq!(identities[0].0, identities[1].0); - assert!(identities[0].1.is_none(), "canonical attempt has no order"); - assert!(identities[1].1.is_some(), "presentation attempt has order"); + } - // Returning to each already-attempted mode reuses its own result - // instead of recomputing or crossing modes. - session.update(&source).into_result().unwrap(); - session.merge().unwrap_err(); - session - .update_for_presentation(&source) - .into_result() + fn add_trusted_strbuf(assembler: &mut crate::DiscoverySourceAssembler) { + assembler + .add_explicit( + "/sdk/strbuf.rue", + "/sdk/strbuf.rue", + crate::PhysicalFileIdentity::new(3, 3), + continuation_metadata(), + Arc::new("pub struct StrBuf { len: i64 }".to_owned()), + ) .unwrap(); - session.merge().unwrap_err(); - assert_eq!(session.work().merge.executions, 2); } #[test] - fn root_relocation_file_id_and_logical_changes_invalidate_correctly() { - let base = snapshot( - &[ - (1, "/old/a.rue", "a.rue", "fn a() {}"), - (2, "/old/b.rue", "b.rue", "fn b() {}"), - ], - 1, - ); - let root_only = snapshot( - &[ - (1, "/old/a.rue", "a.rue", "fn a() {}"), - (2, "/old/b.rue", "b.rue", "fn b() {}"), - ], - 2, - ); - let relocated = snapshot( - &[ - (1, "/new/a.rue", "a.rue", "fn a() {}"), - (2, "/new/b.rue", "b.rue", "fn b() {}"), - ], - 2, - ); - let reassigned = snapshot( - &[ - (11, "/new/a.rue", "a.rue", "fn a() {}"), - (12, "/new/b.rue", "b.rue", "fn b() {}"), - ], - 12, - ); - let renamed = snapshot( - &[ - (11, "/new/a2.rue", "a2.rue", "fn a() {}"), - (12, "/new/b.rue", "b.rue", "fn b() {}"), - ], - 12, - ); - let mut session = CompilerSession::new(); - session.update(&base).into_result().unwrap(); - session.canonical_rir().unwrap(); + fn trusted_successor_publishes_additive_leaf_in_same_generation() { + let (mut session, token, frontier, predecessor, _reads, mut assembler) = + closed_continuation(); + let predecessor_modules = predecessor.source_revision().modules().to_vec(); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let successor_reads = assembler.accepted_read_manifest(); - let root = session.update(&root_only); - assert!(root.downstream_invalidated()); - assert_eq!(root.work().modules_reused, 2); - root.into_result().unwrap(); - session.canonical_rir().unwrap(); - let moved = session.update(&relocated); - assert!(moved.downstream_invalidated()); - assert_eq!(moved.work().modules_rebound, 2); - moved.into_result().unwrap(); - session.canonical_rir().unwrap(); - let ids = session.update(&reassigned); - assert!(ids.downstream_invalidated()); - assert_eq!(ids.work().modules_reparsed, 0); - assert_eq!(ids.work().modules_rebound, 2); - ids.into_result().unwrap(); - session.canonical_rir().unwrap(); - let rename = session.update(&renamed); - assert!(rename.downstream_invalidated()); - assert_eq!(rename.invalidation().added.len(), 1); - assert_eq!(rename.invalidation().removed.len(), 1); - // ParseModule is keyed by stable logical module identity. A logical - // rename is a removed leaf plus a new demanded leaf, so its syntax is - // recomputed even when the source bytes happen to match. - assert_eq!(rename.work().modules_reparsed, 1); - } + let delta = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, successor_reads) + .expect("a strictly-additive trusted successor publishes"); + // The publish mints an opaque delta authority bound to the appended set. + // Its module identities are private; the successor stage/close derive and + // verify them from the snapshot, so the host cannot edit them here. + let published = delta.revision(); - #[test] - fn source_table_reorder_reprojects_parse_record_without_recomputing_modules() { - let first = snapshot( - &[ - (1, "/a.rue", "a.rue", "fn a() -> i32 { 1 }"), - (2, "/b.rue", "b.rue", "fn b() -> i32 { 2 }"), - ], - 1, - ); - let reordered = snapshot( - &[ - (2, "/b.rue", "b.rue", "fn b() -> i32 { 2 }"), - (1, "/a.rue", "a.rue", "fn a() -> i32 { 1 }"), - ], - 1, + // Same request generation as the predecessor close; the frontier round + // advances by one (a successor of that same observation epoch). + assert_eq!( + published.request_generation, + frontier.revision().request_generation ); - assert_eq!(first.source_revision(), reordered.source_revision()); - assert_eq!(first.metadata(), reordered.metadata()); - - let mut session = CompilerSession::new(); - let initial_update = session.update(&first); - let initial_diagnostics = initial_update.diagnostics().clone(); - initial_update.into_result().unwrap(); - let initial_program = session.published.as_ref().unwrap().clone(); - let initial_merge = session.merge().unwrap(); - let initial_rir = session.rir().unwrap(); - - let update = session.update(&reordered); - assert_eq!(update.work().modules_reparsed, 0); - assert_eq!(update.work().modules_reused, 2); - assert!(!update.downstream_invalidated()); - update.result().as_ref().unwrap(); - let current_program = session.published.as_ref().unwrap(); - assert!(Arc::ptr_eq(&initial_program, current_program)); - assert!(!Arc::ptr_eq(&initial_diagnostics, update.diagnostics())); assert_eq!( - update - .diagnostics() - .source() - .files() - .map(|source| source.file_id) - .collect::>(), - [FileId::new(2), FileId::new(1)] + published.frontier_round, + frontier.revision().frontier_round + 1 ); + + // Every pre-existing module leaf is preserved byte-identical. Its exact + // ModuleRevision — and therefore its SourceId, the parse key — reappears in + // the successor, so no pre-existing module is re-read or reparsed across + // acquisition; only the trusted Option leaf is appended. + for old in &predecessor_modules { + assert!( + successor.source_revision().modules().contains(old), + "pre-existing module {old:?} must be preserved byte-identical", + ); + } assert_eq!( - current_program - .modules() - .iter() - .map(|module| (module.module_id().as_str(), module.file_id())) - .collect::>(), - [("a.rue", FileId::new(1)), ("b.rue", FileId::new(2))] + successor.source_revision().modules().len(), + predecessor_modules.len() + 1, ); - assert!(Arc::ptr_eq(&initial_merge, &session.merge().unwrap())); - let current_rir = session.canonical_rir().unwrap(); - assert!(Arc::ptr_eq(initial_rir.owner(), ¤t_rir)); - assert!(initial_rir.owner().structurally_eq(¤t_rir)); + } + + #[test] + fn trusted_successor_reused_token_is_rejected() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + // The first publish consumes the single-use token. + session + .publish_trusted_toolchain_successor( + token.clone(), + &frontier, + &successor, + reads.clone(), + ) + .unwrap(); + // Reusing it finds no outstanding continuation. + let err = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .unwrap_err(); assert!( - current_rir - .rir() - .iter() - .all(|(_, instruction)| { matches!(instruction.span.file_id.index(), 1 | 2) }) + err.first().unwrap().to_string().contains("already used"), + "{err:?}", ); } #[test] - fn semantic_queries_reuse_by_codegen_identity_and_ignore_linker() { - fn assert_send_sync() {} - assert_send_sync::(); - - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let options = CompileOptions::default(); - let first = session.canonical_semantic(&options).unwrap(); - let second = session.canonical_semantic(&options).unwrap(); - assert!(Arc::ptr_eq(&first, &second)); - - let linker_only = CompileOptions { - linker: LinkerMode::System("unused-linker".to_string()), - ..options.clone() - }; - assert!(Arc::ptr_eq( - &first, - &session.canonical_semantic(&linker_only).unwrap() - )); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().semantic.reuses, 2); - assert_eq!(session.work().semantic_entries, 1); - assert_eq!(session.work().merge.executions, 1); - assert_eq!(session.work().rir.executions, 1); - assert_eq!(first.work().binding.bind_invocations, 1); - assert_eq!(first.work().manifest.build_invocations, 1); - - let published = first.clone(); - std::thread::spawn(move || assert!(!published.functions().is_empty())) - .join() - .unwrap(); + fn trusted_successor_stale_token_is_rejected() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + // Simulate a newer close superseding this token: advance the outstanding + // state's nonce so the presented token no longer matches (stale). + session.next_continuation_nonce += 7; + session.continuation.as_mut().unwrap().nonce = session.next_continuation_nonce; + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let err = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .unwrap_err(); + assert!( + err.first().unwrap().to_string().contains("stale"), + "{err:?}" + ); } #[test] - fn semantic_option_variants_create_deterministic_distinct_entries() { - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let default = CompileOptions::default(); - session.canonical_semantic(&default).unwrap(); - session - .canonical_semantic(&CompileOptions { - opt_level: OptLevel::O1, - ..default.clone() - }) - .unwrap(); - let other_target = *Target::all() - .iter() - .find(|&&target| target != default.target) - .expect("multiple compiler targets"); - session - .canonical_semantic(&CompileOptions { - target: other_target, - ..default.clone() - }) - .unwrap(); + fn trusted_successor_new_request_invalidates_the_token() { + let (mut session, token, frontier, predecessor, reads, mut assembler) = + closed_continuation(); + // A fresh import-input request invalidates any outstanding continuation. session - .canonical_semantic(&CompileOptions { - preview_features: PreviewFeatures::from([PreviewFeature::TestInfra]), - ..default - }) + .begin_import_input_request(&predecessor, continuation_std_context(), reads) .unwrap(); - - let work = session.work(); - assert_eq!(work.semantic.executions, 4); - assert_eq!(work.semantic_entries, 4); - assert_eq!(work.semantic_records.len(), 4); - assert!(work.semantic_records.iter().all(|record| { - !record.failed - && record.work.binding.bind_invocations == 1 - && record.work.manifest.build_invocations == 1 - })); - for (index, left) in work.semantic_records.iter().enumerate() { - assert!( - work.semantic_records[index + 1..] - .iter() - .all(|right| left.input != right.input) - ); - } + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let successor_reads = assembler.accepted_read_manifest(); + let err = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, successor_reads) + .unwrap_err(); + assert!( + err.first().unwrap().to_string().contains("already used"), + "{err:?}", + ); } #[test] - fn semantic_reuse_origin_is_the_exact_typed_terminal_across_multiple_keys() { - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let a = CompileOptions::default(); - let b = CompileOptions { - opt_level: OptLevel::O1, - ..a.clone() - }; - session.canonical_semantic(&a).unwrap(); - session.canonical_semantic(&b).unwrap(); - session.canonical_semantic(&a).unwrap(); - let attempts = session - .metrics - .attempts() - .into_iter() - .filter(|attempt| attempt.family == "semantic") - .collect::>(); - assert_eq!(attempts.len(), 3); - assert_eq!( - attempts[0].attempt.execution(), - QueryAttemptExecution::Computed - ); - assert_eq!( - attempts[1].attempt.execution(), - QueryAttemptExecution::Computed - ); - assert_eq!( - attempts[2].attempt.execution(), - QueryAttemptExecution::Reused + fn trusted_successor_mutated_predecessor_is_rejected() { + let (mut session, token, frontier, _pred, _reads, _assembler) = closed_continuation(); + // A successor whose pre-existing root content differs is a mutated + // predecessor: source evolution must be strictly additive. + let ctx = continuation_std_context(); + let mut other = crate::DiscoverySourceAssembler::new( + ctx, + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + Arc::new("fn main() -> i32 { 1 }".to_owned()), + ) + .unwrap(); + add_trusted_option(&mut other); + let successor = other.snapshot().unwrap(); + let reads = other.accepted_read_manifest(); + let err = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .unwrap_err(); + assert!( + err.first() + .unwrap() + .to_string() + .contains("strictly additive"), + "{err:?}", ); - assert_eq!(attempts[2].attempt.origin_id(), attempts[0].attempt.id()); - assert_ne!(attempts[2].attempt.origin_id(), attempts[1].attempt.id()); } #[test] - fn retained_terminal_origin_survives_attempt_ledger_rollover() { - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let options = CompileOptions::default(); - session.canonical_semantic(&options).unwrap(); - let origin = session - .metrics - .attempts() - .into_iter() - .find(|attempt| attempt.family == "semantic") - .unwrap() - .attempt - .id(); - for _ in 0..(QUERY_ATTEMPT_RETENTION_LIMIT + 44) { - session.import_graph(None).unwrap(); - } - session.canonical_semantic(&options).unwrap(); - let attempts = session.metrics.attempts(); + fn trusted_successor_arbitrary_module_is_rejected() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + // StrBuf is a trusted module the park did NOT demand here (the reached + // `@parse_i64` parks on Option only), so the added set {StrBuf} does not + // equal the demanded set {Option} and may not ride in on this continuation. + add_trusted_strbuf(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let err = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .unwrap_err(); assert!( - attempts - .iter() - .any(|attempt| attempt.attempt.id() == origin) + err.first() + .unwrap() + .to_string() + .contains("must equal the rooted park's demanded missing set"), + "{err:?}", ); - let reused = attempts - .iter() - .rev() - .find(|attempt| attempt.family == "semantic") - .unwrap(); - assert_eq!(reused.attempt.execution(), QueryAttemptExecution::Reused); - assert_eq!(reused.attempt.origin_id(), origin); } #[test] - fn dependency_manifest_retention_is_bounded_and_recomputes_fifo() { - let source = base(); + fn trusted_successor_ready_close_is_non_authorizing() { + // A close whose rooted semantic attempt is READY (no fallible intrinsic, + // no park) attaches no demanded set, so the closed continuation mints no + // token. Demand authority lives only in an attached park, so a ready close + // can never inherit an earlier park's demand set and admit an uninvited + // trusted leaf. + let ctx = continuation_std_context(); + let mut assembler = crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + Arc::new("fn main() -> i32 { 0 }".to_owned()), + ) + .unwrap(); + let snapshot = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let variants = retention_variants(); - for options in &variants { - session.semantic_dependency_inputs(options, None).unwrap(); - } - assert_eq!( - session.queries.manifests.len(), - QUERY_TERMINAL_RETENTION_LIMIT - ); - assert_eq!( - session.work().dependency_manifests.executions, - variants.len() - ); - session - .semantic_dependency_inputs(&variants[0], None) + let revision = session + .begin_import_input_request(&snapshot, ctx.clone(), reads.clone()) .unwrap(); - assert_eq!( - session.work().dependency_manifests.executions, - variants.len() + 1 - ); - session - .semantic_dependency_inputs(&variants[0], None) + let plan = session + .stage_import_discovery( + &snapshot, + ctx.clone(), + reads.shared_slice(), + crate::ImportObservationLedger::default(), + ) + .unwrap(); + let roots = plan.demand_roots(); + let _frontier = session + .import_demand_frontier_for_roots( + revision, + &plan, + crate::ImportDemandMode::Rooted, + &roots, + ) .unwrap(); - assert_eq!(session.work().dependency_manifests.reuses, 1); - assert!(session.metrics.attempts().len() <= QUERY_ATTEMPT_RETENTION_LIMIT); + let ledger = session.import_observation_ledger(revision).unwrap(); + session.close_import_discovery(ledger).unwrap(); + // The rooted attempt is ready: no park, so no demanded set is attached. + assert!(matches!( + session.semantic_or_toolchain_park(&CompileOptions::default()), + SemanticParkOutcome::Ready(_) + )); + assert!( + session.closed_discovery_continuation().is_none(), + "a ready close is non-authorizing and mints no continuation token", + ); } #[test] - fn semantic_store_eviction_recomputes_then_reuses_with_exact_metrics() { - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let variants = retention_variants(); - - for options in &variants { - session.canonical_semantic(options).unwrap(); - } - assert_eq!(session.work().semantic.calls, variants.len()); - assert_eq!(session.work().semantic.executions, variants.len()); - assert_eq!(session.work().semantic.reuses, 0); - assert_eq!( - session.work().retention.semantic_query_entries, - QUERY_TERMINAL_RETENTION_LIMIT + fn trusted_successor_partial_batch_is_rejected_without_consuming_token() { + // A reached `@read_line` parks on BOTH Option and StrBuf. A successor that + // adds only Option is a partial batch — added {Option} does not equal the + // demanded {Option, StrBuf} — so it is rejected. A rejection never consumes + // the single-use token, so completing the batch and retrying with the same + // token then publishes. + let (mut session, token, frontier, _pred, _reads, mut assembler) = + closed_continuation_for("fn main() -> i32 { let _ = @read_line(); 0 }"); + add_trusted_option(&mut assembler); + let partial = assembler.snapshot().unwrap(); + let partial_reads = assembler.accepted_read_manifest(); + let err = session + .publish_trusted_toolchain_successor(token.clone(), &frontier, &partial, partial_reads) + .unwrap_err(); + assert!( + err.first() + .unwrap() + .to_string() + .contains("must equal the rooted park's demanded missing set"), + "{err:?}", ); - assert_eq!(session.work().retention.semantic_query_evictions, 1); - - session.canonical_semantic(&variants[0]).unwrap(); - assert_eq!(session.work().semantic.calls, variants.len() + 1); - assert_eq!(session.work().semantic.executions, variants.len() + 1); - assert_eq!(session.work().semantic.reuses, 0); - assert_eq!(session.work().retention.semantic_query_evictions, 2); - - session.canonical_semantic(&variants[0]).unwrap(); - assert_eq!(session.work().semantic.calls, variants.len() + 2); - assert_eq!(session.work().semantic.executions, variants.len() + 1); - assert_eq!(session.work().semantic.reuses, 1); - assert_eq!(session.work().semantic_records.len(), variants.len() + 1); - assert_eq!(session.work().retention.semantic_query_evictions, 2); + // The token survived the rejection; completing the two-module batch and + // retrying publishes with the SAME token. + add_trusted_strbuf(&mut assembler); + let full = assembler.snapshot().unwrap(); + let full_reads = assembler.accepted_read_manifest(); + session + .publish_trusted_toolchain_successor(token, &frontier, &full, full_reads) + .expect("the completed two-module batch publishes with the un-consumed token"); } #[test] - fn semantic_cache_invalidates_on_edit_but_survives_failed_parse() { - let source = base(); - let edited = snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, - ); - let broken = snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main( {"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, + fn trusted_successor_altered_predecessor_provenance_is_rejected() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let full_reads = assembler.accepted_read_manifest(); + // Drop the predecessor root's accepted-read provenance, keeping only the + // added leaf's: the old provenance is no longer byte-identical. + let tampered: Vec<_> = full_reads + .iter() + .filter(|entry| entry.module().is_trusted_standard_library()) + .cloned() + .collect(); + let err = session + .publish_trusted_toolchain_successor( + token, + &frontier, + &successor, + crate::AcceptedReadManifest::from_entries(tampered), + ) + .unwrap_err(); + assert!( + err.first() + .unwrap() + .to_string() + .contains("altered or removed"), + "{err:?}", ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let first = session.canonical_semantic(&options).unwrap(); - assert!(session.update(&broken).result().is_err()); - assert!(Arc::ptr_eq( - &first, - &session.canonical_semantic(&options).unwrap() - )); - let update = session.update(&edited); - assert!(update.downstream_invalidated()); - update.into_result().unwrap(); - let second = session.canonical_semantic(&options).unwrap(); - assert!(!Arc::ptr_eq(&first, &second)); - assert_eq!(session.work().semantic.executions, 2); - assert_eq!(session.work().semantic_entries_invalidated, 1); } + /// A successor-delta capability minted by one session cannot authorize a + /// successor stage on a different session: the delta is bound to its issuing + /// session, so a cross-session value is rejected without staging anything. #[test] - fn dependency_edges_preserve_noops_and_restore_exact_retained_terminals() { - let source = base(); - let edited = snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let first = session.canonical_semantic(&options).unwrap(); - let original_key = session - .queries - .semantic - .records() - .next() - .unwrap() - .key - .clone(); - let original_handle = session.queries.semantic.handle(&original_key).unwrap(); - - let noop = session.update(&source); - assert!(!noop.downstream_invalidated()); - assert!(Arc::ptr_eq( - &first, - &session.canonical_semantic(&options).unwrap() - )); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().semantic.reuses, 1); - - let changed = session.update(&edited); - assert!(changed.downstream_invalidated()); - changed.into_result().unwrap(); - assert!(!original_handle.is_valid(&mut session.queries.graph)); - assert!(original_handle.invalidation_cause_count(&session.queries.graph) > 0); - assert_eq!(session.work().semantic_entries_invalidated, 1); - let second = session.canonical_semantic(&options).unwrap(); - assert!(!Arc::ptr_eq(&first, &second)); + fn successor_delta_from_another_session_is_rejected() { + let (mut issuer, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let delta = issuer + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads.clone()) + .expect("a strictly-additive successor publishes"); - session.update(&source).into_result().unwrap(); - assert!(original_handle.is_valid(&mut session.queries.graph)); - assert_eq!( - original_handle.invalidation_cause_count(&session.queries.graph), - 0 + let mut other = CompilerSession::new(); + let err = other.stage_import_discovery_successor(&delta).unwrap_err(); + assert!( + err.first() + .unwrap() + .to_string() + .contains("different session"), + "{err:?}", ); - assert!(Arc::ptr_eq( - &first, - &session.canonical_semantic(&options).unwrap() - )); - assert_eq!(session.work().semantic.executions, 2); - assert_eq!(session.work().semantic.reuses, 2); - assert_eq!(session.work().merge.executions, 2); - assert_eq!(session.work().rir.executions, 2); } + /// A successor-delta capability is single-generation: a new import-input + /// request invalidates it, so a stale delta can neither stage nor close. #[test] - fn semantic_errors_are_memoized_and_recovery_reexecutes() { - let invalid = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn main() -> i32 { missing_name }", - )], - 1, - ); - let valid = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&invalid).into_result().unwrap(); - let first = session.canonical_semantic(&options).unwrap_err(); - let second = session.canonical_semantic(&options).unwrap_err(); - assert_eq!(format!("{first:?}"), format!("{second:?}")); - assert_eq!(session.work().semantic.calls, 2); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().semantic.reuses, 1); - let semantic_attempts = session - .metrics - .attempts() - .into_iter() - .filter(|attempt| attempt.family == "semantic") - .collect::>(); - assert_eq!(semantic_attempts.len(), 2); - assert!(semantic_attempts.iter().all(|attempt| { - attempt.attempt.outcome() == crate::typed_query_store::AttemptOutcomeKind::Failure - })); - assert_eq!( - semantic_attempts[1].attempt.origin_id(), - semantic_attempts[0].attempt.id() - ); - assert_eq!(session.work().semantic_records.len(), 1); - assert!(session.work().semantic_records[0].failed); - let retained_failed_work = session.work().semantic_records[0].work; + fn stale_successor_delta_cannot_stage() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let delta = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads.clone()) + .expect("a strictly-additive successor publishes"); - session.update(&valid).into_result().unwrap(); - assert!(session.canonical_semantic(&options).is_ok()); - assert_eq!(session.work().semantic.calls, 3); - assert_eq!(session.work().semantic.executions, 2); - assert_eq!(session.work().semantic.reuses, 1); - assert_eq!(session.work().semantic_entries, 2); - assert_eq!(session.work().semantic_entries_invalidated, 1); - assert_eq!( - retained_failed_work.cfg.cfg_builds_attempted, 0, - "a failed body terminal must stop before CFG construction" + // A fresh observation generation invalidates the outstanding delta. + session + .begin_import_input_request(&successor, continuation_std_context(), reads.clone()) + .unwrap(); + let err = session + .stage_import_discovery_successor(&delta) + .unwrap_err(); + assert!( + err.first() + .unwrap() + .to_string() + .contains("no outstanding successor-delta authority"), + "{err:?}", ); } + /// The successor parse terminal is a REAL runtime query dependent of the + /// exact predecessor parse terminal — the graph carries the + /// successor-after-predecessor edge — and the successor close re-selects + /// the staged terminal itself: same terminal identity, no second parse + /// dispatch, and no second empty-extension publication. #[test] - fn deterministic_body_failure_is_terminal_and_recovers_without_replacing_last_good() { - let valid = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, + fn successor_close_reuses_the_staged_terminal_with_a_predecessor_edge() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + let predecessor_terminal = session + .selected_parse_terminal() + .expect("the committed close selects its staged parse terminal"); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let delta = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .expect("a strictly-additive successor publishes"); + session + .stage_import_discovery_successor(&delta) + .expect("the successor stages"); + let staged_terminal = session + .selected_parse_terminal() + .expect("the successor stage selects its parse terminal"); + assert!( + !Arc::ptr_eq(&predecessor_terminal, &staged_terminal), + "the successor stage computes its own terminal" ); - let invalid = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn main() -> i32 { missing_name }", - )], - 1, + // (a) The successor terminal observes the exact predecessor parse + // terminal as a runtime query dependency — the FULL captured identity + // (node, incarnation, AND stamp), not an equivalent replacement under + // the same display node — so red/green validation and leases flow + // successor-after-predecessor through the graph. + let observation = staged_terminal + .dependencies() + .iter() + .find(|dependency| dependency.node == *predecessor_terminal.node()) + .unwrap_or_else(|| { + panic!( + "the successor terminal must depend on the exact predecessor parse terminal: {:?}", + staged_terminal.dependencies(), + ) + }); + assert_eq!( + observation.incarnation, + predecessor_terminal.node_incarnation(), + "the dependency must carry the captured terminal's exact node incarnation" ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); - let baseline = session.canonical_semantic(&options).unwrap(); - let key = body_query_key(&mut session, &options, "main"); - - session.update(&invalid).into_result().unwrap(); - let first = session.canonical_semantic(&options).unwrap_err(); - let second = session.canonical_semantic(&options).unwrap_err(); - assert_eq!(format!("{first:?}"), format!("{second:?}")); - let record = session.work().semantic_records.last().unwrap(); assert_eq!( - record.failure.unwrap().phase, - CanonicalSemanticFailurePhase::BodyAnalysis + observation.stamp, + predecessor_terminal.stamp(), + "the dependency must carry the captured terminal's exact stamp" ); - assert_eq!(record.work.cfg.cfg_builds_attempted, 0); - let (first_stamp, first_kind, first_transaction) = - retained_body_transaction(&session, &key); - assert_eq!(first_kind, rue_query::QueryTerminalKind::Failure); - assert!(matches!( - first_transaction, - crate::body_query::BodyTransaction::DeterministicFailure { .. } - )); - let (reused_stamp, reused_kind, reused_transaction) = - retained_body_transaction(&session, &key); - assert_eq!(first_stamp, reused_stamp); - assert_eq!(reused_kind, rue_query::QueryTerminalKind::Failure); - assert!(matches!( - reused_transaction, - crate::body_query::BodyTransaction::DeterministicFailure { .. } - )); - - session.update(&valid).into_result().unwrap(); - let recovered = session.canonical_semantic(&options).unwrap(); + // That the adoption touched no predecessor content-key Hash/Eq is + // proven mechanically by the rue-query frozen-key regression + // (`adoption_never_hashes_or_compares_the_predecessor_key`). + // (b) The close re-selects the staged terminal itself: identical + // terminal identity, no parse dispatch, and no second publication. + let dispatched = session.parse_modules_dispatched(); + let materialized = session.parse_sources_materialized(); + session + .close_import_discovery_successor(&delta) + .expect("the successor closes"); + let adopted_terminal = session + .selected_parse_terminal() + .expect("the successor close selects the staged parse terminal"); assert!( - Arc::ptr_eq(&recovered, &baseline), - "restoring the exact source leaf must reinstate its retained terminal" - ); - let (success_stamp, success_kind, success_transaction) = - retained_body_transaction(&session, &key); - assert_ne!(first_stamp, success_stamp); - assert_eq!(success_kind, rue_query::QueryTerminalKind::Success); - assert!(matches!( - success_transaction, - crate::body_query::BodyTransaction::Success { .. } - )); - } - - #[test] - fn retained_body_failure_reprojects_spans_after_leading_trivia_edit() { - let first_text = "fn main() -> i32 { missing_name }"; - let shifted_text = "// newly inserted leading trivia\n\nfn main() -> i32 { missing_name }"; - let first = snapshot(&[(1, "/p/main.rue", "main.rue", first_text)], 1); - let shifted = snapshot(&[(1, "/p/main.rue", "main.rue", shifted_text)], 1); - let valid = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - - session.update(&valid).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - let key = body_query_key(&mut session, &options, "main"); - session.update(&first).into_result().unwrap(); - let first_errors = session.canonical_semantic(&options).unwrap_err(); - let (first_stamp, _, _) = retained_body_transaction(&session, &key); - assert_eq!( - first_errors - .first() - .and_then(|error| error.span()) - .unwrap() - .start, - u32::try_from(first_text.find("missing_name").unwrap()).unwrap(), + Arc::ptr_eq(&staged_terminal, &adopted_terminal), + "the successor close must re-select the exact staged parse terminal" ); - - session.update(&shifted).into_result().unwrap(); - let shifted_errors = session.canonical_semantic(&options).unwrap_err(); - let (shifted_stamp, _, _) = retained_body_transaction(&session, &key); assert_eq!( - shifted_stamp, first_stamp, - "a locator-only edit must reuse the semantic body transaction", + session.parse_modules_dispatched(), + dispatched, + "the successor close dispatches no parse work" ); - let shifted_span = shifted_errors - .first() - .and_then(|error| error.span()) - .unwrap(); assert_eq!( - shifted_span.start, - u32::try_from(shifted_text.find("missing_name").unwrap()).unwrap(), + session.parse_sources_materialized(), + materialized, + "the successor close materializes no whole-program projection" ); - assert_eq!(shifted_span.file_id, crate::FileId::new(1)); } + /// A strictly-additive successor adoption must leave the predecessor's + /// immutable source leaf live: retained frontend terminals that correctly + /// depend on it (however many variants are prewarmed) stay valid, and the + /// acquisition contributes ZERO dependency-graph invalidation events — + /// the successor becomes current without walking or invalidating the + /// predecessor's retained downstream. #[test] - fn anonymous_member_diagnostic_uses_its_exact_producer_location() { - let text = "fn Box(comptime T: type) -> type { struct { value: T, fn bad(self) -> i32 { missing_name } } } fn main() -> i32 { let B = Box(i32); let value = B { value: 1 }; value.bad() }"; - let shifted_text = format!("// leading trivia relocates the producer\n\n{text}"); - let valid_text = text.replace("missing_name", "0"); - let source = snapshot(&[(1, "/p/main.rue", "main.rue", text)], 1); - let shifted = snapshot(&[(1, "/p/main.rue", "main.rue", shifted_text.as_str())], 1); - let valid = snapshot(&[(1, "/p/main.rue", "main.rue", valid_text.as_str())], 1); - let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - session.update(&source).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - session.update(&shifted).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - let body_work = session - .work() - .semantic_records - .last() - .unwrap() - .work - .body_analysis; + fn successor_adoption_invalidates_no_retained_frontend_variants() { + let acquisition_invalidations = |prewarm_retained_downstream: bool| -> u64 { + let (mut session, token, frontier, _pred, _reads, mut assembler) = + closed_continuation(); + if prewarm_retained_downstream { + // Retain additional terminals depending on the predecessor's + // source leaf. Semantic — and the definition/manifest variants + // that observe it — cannot complete on this predecessor (the + // reached fallible intrinsic parks semantic until + // acquisition), so the retained downstream of the leaf is the + // pre-semantic tier: merged RIR and canonical import + // diagnostics. + session + .rir() + .expect("pre-semantic RIR completes on the parked predecessor"); + session + .import_diagnostics() + .expect("import diagnostics retain on the closed predecessor"); + } + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let delta = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .expect("a strictly-additive successor publishes"); + let before = session.frontend_query_invalidations(); + session + .stage_import_discovery_successor(&delta) + .expect("the successor stages"); + session + .close_import_discovery_successor(&delta) + .expect("the successor closes"); + session.frontend_query_invalidations() - before + }; + let bare = acquisition_invalidations(false); + let prewarmed = acquisition_invalidations(true); assert_eq!( - body_work.body_analyses_computed, 0, - "leading trivia must not recompute the anonymous member transaction", - ); - assert!( - body_work.body_analyses_reused >= 2, - "the producer and anonymous member transactions are both reused", + bare, 0, + "additive successor adoption must not invalidate retained frontend terminals" ); - let error = errors - .iter() - .find(|error| { - error.span().is_some_and(|span| { - span.start as usize == shifted_text.find("missing_name").unwrap() - }) - }) - .unwrap_or_else(|| panic!("no diagnostic at the anonymous member: {errors:?}")); - let span = error.span().unwrap(); - assert_eq!(span.file_id, crate::FileId::new(1)); assert_eq!( - &shifted_text[span.start as usize..span.end as usize], - "missing_name" + prewarmed, 0, + "additive successor adoption must not invalidate retained frontend terminals regardless of how much retained downstream depends on the predecessor leaf" ); } + /// A successor-delta capability outstanding across an intervening source or + /// presentation update is invalidated: the update replaced the retained + /// parse artifact the successor would extend, so the stale capability can + /// neither stage nor close — a mixed parsed program (foreign retained + /// modules under the successor's claimed source revision) is never + /// produced. #[test] - fn const_produced_anonymous_member_is_scheduled_and_relocates_diagnostics() { - let invalid_text = "const B: type = struct { value: i32, fn bad(self) -> i32 { missing_name } }; fn main() -> i32 { let value = B { value: 1 }; value.bad() }"; - let valid_text = invalid_text.replace("missing_name", "0"); - let shifted_text = format!("// relocate the const producer\n\n{invalid_text}"); - let valid = snapshot(&[(1, "/p/main.rue", "main.rue", valid_text.as_str())], 1); - let invalid = snapshot(&[(1, "/p/main.rue", "main.rue", invalid_text)], 1); - let shifted = snapshot(&[(1, "/p/main.rue", "main.rue", shifted_text.as_str())], 1); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - - session.update(&valid).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - session.update(&invalid).into_result().unwrap(); - session.canonical_semantic(&options).unwrap_err(); - session.update(&shifted).into_result().unwrap(); - let errors = session.canonical_semantic(&options).unwrap_err(); - let span = errors - .iter() - .filter_map(|error| error.span()) - .find(|span| span.start as usize == shifted_text.find("missing_name").unwrap()) - .unwrap_or_else(|| panic!("no diagnostic at const-produced member: {errors:?}")); - assert_eq!(span.file_id, crate::FileId::new(1)); - assert_eq!( - &shifted_text[span.start as usize..span.end as usize], - "missing_name", - ); - let body_work = session - .work() - .semantic_records - .last() - .unwrap() - .work - .body_analysis; - assert_eq!(body_work.body_analyses_computed, 0); - assert!(body_work.body_analyses_reused >= 2); - } + fn intervening_presentation_update_invalidates_successor_delta() { + let (mut session, token, frontier, _pred, _reads, mut assembler) = closed_continuation(); + add_trusted_option(&mut assembler); + let successor = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); + let delta = session + .publish_trusted_toolchain_successor(token, &frontier, &successor, reads) + .expect("a strictly-additive successor publishes"); - #[test] - fn specialization_failure_work_is_retained() { - let invalid = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn runaway(comptime n: i32) -> i32 { runaway(n + 1) }\nfn main() -> i32 { runaway(0) }", - )], + // An intervening presentation update installs a successful parse of a + // DIFFERENT snapshot (unrelated content and file order). + let foreign = snapshot( + &[ + (2, "/q/aux.rue", "aux.rue", "pub fn v() -> i32 { 2 }"), + (1, "/q/main.rue", "main.rue", "fn main() -> i32 { 0 }"), + ], 1, ); - let mut session = CompilerSession::new(); - session.update(&invalid).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - assert!(matches!( - errors.first().map(|error| &error.kind), - Some(ErrorKind::ComptimeEvaluationFailed { reason }) - if reason.contains("maximum nesting depth") - )); - let record = session.work().semantic_records.last().unwrap(); - assert_eq!( - record.failure.unwrap().phase, - CanonicalSemanticFailurePhase::BodyAnalysis - ); - // The retired whole-program specialization driver did not run. The - // query coordinator owns the bounded frontier and reports overflow - // atomically instead of publishing a partial closure. - assert_eq!(record.work.body_analysis.specialization_rounds, 0); - } - - #[test] - fn reused_specializations_consume_the_persistent_round_budget() { - let source = |requested: i32| { - let program = format!( - "fn chain(comptime n: i32) -> i32 {{\n\ - if n == 0 {{ 0 }} else {{ chain(n - 1) }}\n\ - }}\n\ - fn main() -> i32 {{ chain({requested}) }}" - ); - snapshot(&[(1, "/p/main.rue", "main.rue", program.as_str())], 1) - }; - let baseline = source(63); - let overflowing = source(64); - let mut session = CompilerSession::new(); - session.update(&baseline).into_result().unwrap(); session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); + .update_for_presentation(&foreign) + .into_result() + .expect("the foreign presentation update parses"); - session.update(&overflowing).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) + let stage_err = session + .stage_import_discovery_successor(&delta) .unwrap_err(); - assert!(matches!( - errors.first().map(|error| &error.kind), - Some(ErrorKind::ComptimeEvaluationFailed { reason }) - if reason.contains("maximum nesting depth") - )); - let failure = session.work().semantic_records.last().unwrap(); - assert_eq!( - failure.failure.unwrap().phase, - CanonicalSemanticFailurePhase::BodyAnalysis + assert!( + stage_err + .first() + .unwrap() + .to_string() + .contains("no outstanding successor-delta authority"), + "{stage_err:?}", + ); + let close_err = session + .close_import_discovery_successor(&delta) + .unwrap_err(); + assert!( + close_err + .first() + .unwrap() + .to_string() + .contains("no outstanding successor-delta authority"), + "{close_err:?}", ); - session.update(&baseline).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions { - opt_level: OptLevel::O2, - ..CompileOptions::default() - }) - .unwrap(); - - session - .canonical_semantic(&CompileOptions { - opt_level: OptLevel::O3, - ..CompileOptions::default() - }) - .unwrap(); } - #[test] - fn many_shallow_specializations_compile() { - // Regression (RUE-1083): breadth is not depth. A program may reach far - // more than `MAX_SPECIALIZATION_ROUNDS` distinct specializations as long - // as each sits at a shallow instantiation depth. Here `tag` has a - // compile-time-known base case, so every `tag(k)` is a leaf - // specialization at nesting depth 1; `main` reaches - // `MAX_SPECIALIZATION_ROUNDS + 8` of them. The retired total-count budget - // failed this program with E1200; the chain-depth budget compiles it. - let count = rue_air::specialize::MAX_SPECIALIZATION_ROUNDS + 8; - let mut body = String::from("fn main() -> i32 {\n let mut total = 0;\n"); - for k in 0..count { - body.push_str(&format!(" total = total + tag({k});\n")); - } - body.push_str(" total\n}\n"); - let program = format!("fn tag(comptime n: i32) -> i32 {{ n }}\n{body}"); - let valid = snapshot(&[(1, "/p/main.rue", "main.rue", program.as_str())], 1); - let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); + /// Substituted snapshots, contexts, provenance manifests, and ledgers are + /// INEXPRESSIBLE at the successor stage/close: those APIs consume only the + /// compiler-published view and the opaque capability. The one remaining host + /// input surface on a same-generation lineage is the observation-batch + /// publication, so the tampering regressions below attack through it; the + /// overlay publication re-derives and justifies every addition, rejecting + /// each attack before anything is published. + /// + /// Run one tampered batch publication against a closed lineage whose rooted + /// frontier witness is empty, returning the rejection text. + fn tampered_batch_error( + build: impl FnOnce(&crate::ImportDiscoveryContext) -> crate::DiscoverySourceAssembler, + ) -> String { + let (mut session, _token, frontier, _pred, _reads, _assembler) = closed_continuation(); + let ctx = continuation_std_context(); + let mut tampered = build(&ctx); + let snapshot = tampered.snapshot().unwrap(); + let reads = tampered.accepted_read_manifest(); session - .canonical_semantic(&CompileOptions::default()) - .expect("many shallow specializations must compile"); + .publish_import_observation_batch(&frontier, &snapshot, reads, Vec::new()) + .unwrap_err() + .to_string() } + /// A batch cannot INJECT a module: a snapshot carrying a module no accepted + /// observation of that batch resolves is rejected at publication, so an + /// unrelated module can never enter the published lineage (and therefore can + /// never reach a successor stage/close, which read only the published view). #[test] - fn cross_body_specialization_chain_still_overflows() { - // The chain-depth budget must still reject unbounded cross-body - // instantiation chains: `deepen` instantiates `deepen`, so - // each body publishes a strictly deeper specialization and the nesting - // depth grows without bound. This must fail with the same E1200 - // (`maximum nesting depth`) diagnostic as before. - let invalid = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "fn deepen(comptime n: i32) -> i32 { deepen(n + 1) }\n\ - fn main() -> i32 { deepen(0) }", - )], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&invalid).into_result().unwrap(); - let errors = session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); + fn observation_batch_rejects_an_injected_module() { + let error = tampered_batch_error(|ctx| { + let mut assembler = crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + Arc::new("fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }".to_owned()), + ) + .unwrap(); + // An extra module with provenance but NO justifying observation. + add_trusted_option(&mut assembler); + assembler + }); assert!( - matches!( - errors.first().map(|error| &error.kind), - Some(ErrorKind::ComptimeEvaluationFailed { reason }) - if reason.contains("maximum nesting depth") - ), - "runaway cross-body specialization chain must overflow with E1200" + error.contains("must equal this step's authorized additions exactly"), + "{error}", ); } + /// A batch cannot MUTATE a predecessor module under its ID: a snapshot whose + /// root module has the same identity but different content is rejected at + /// publication (the lineage is strictly additive at that boundary). #[test] - fn declaration_failure_retains_completed_declaration_work() { - let valid = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); - let changed_source = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }")], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - session.update(&changed_source).into_result().unwrap(); - crate::canonical_semantic::with_test_declaration_failure_injection(|| { - session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); + fn observation_batch_rejects_a_mutated_predecessor_source() { + let error = tampered_batch_error(|ctx| { + crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + // Same root identity, DIFFERENT body. + Arc::new("fn main() -> i32 { let _ = @parse_i64(\"1\"); 42 }".to_owned()), + ) + .unwrap() }); - - let record = session.work().semantic_records.last().unwrap(); - assert_eq!( - record.failure.unwrap().phase, - CanonicalSemanticFailurePhase::Declaration + assert!( + error.contains("mutates a predecessor module source"), + "{error}", ); - assert_eq!(record.work.declaration_index.build_invocations, 1); - assert_eq!(record.work.binding.bind_invocations, 1); - assert_eq!(record.work.manifest.build_invocations, 1); } + /// A batch cannot OMIT an accepted module: publishing the exact + /// compiler-issued accepted observation for a newly resolved module while + /// omitting that module from the successor snapshot is rejected — the + /// additions must EQUAL the batch's accepted resolutions in both + /// directions, so topology can never claim "resolved" without the module's + /// source leaf behind it. #[test] - fn declaration_resolution_failure_retains_exact_attempt_work() { - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "struct Recursive { next: Recursive } fn main() -> i32 { 0 }", - )], - 1, - ); + fn observation_batch_rejects_omitting_an_accepted_module() { + let ctx = continuation_std_context(); + let root_source = "const a = @import(\"a.rue\"); fn main() -> i32 { 0 }"; + let mut assembler = crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + Arc::new(root_source.to_owned()), + ) + .unwrap(); + let snapshot = assembler.snapshot().unwrap(); + let reads = assembler.accepted_read_manifest(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); + let revision = session + .begin_import_input_request(&snapshot, ctx.clone(), reads.clone()) + .unwrap(); + let plan = session + .stage_import_discovery( + &snapshot, + ctx.clone(), + reads.shared_slice(), + crate::ImportObservationLedger::default(), + ) + .unwrap(); + let roots = plan.demand_roots(); + let frontier = session + .import_demand_frontier_for_roots( + revision, + &plan, + crate::ImportDemandMode::Rooted, + &roots, + ) + .unwrap(); + assert!( + !frontier.requests().is_empty(), + "an unresolved import demands host reads", + ); - let record = session.work().semantic_records.last().unwrap(); - assert_eq!( - record.failure.unwrap().phase, - CanonicalSemanticFailurePhase::Declaration + // Answer the frontier honestly for a.rue: the exact compiler-issued + // accepted observation, absent elsewhere. + let module_source = "pub fn value() -> i32 { 1 }"; + let observations: Vec = frontier + .requests() + .iter() + .map(|request| { + if request.requested_path() == "/project/a.rue" { + crate::ImportObservation::accepted( + request.clone(), + crate::AcceptedImportSource::new( + Arc::from("/project/a.rue"), + Arc::from("/project/a.rue"), + crate::PhysicalFileIdentity::new(5, 5), + continuation_metadata(), + Arc::new(module_source.to_owned()), + ) + .unwrap(), + ) + .unwrap() + } else { + crate::ImportObservation::absent(request.clone()) + } + }) + .collect(); + + // A manifest carrying the resolved module's provenance, but a snapshot + // OMITTING the module itself. + let mut with_module = crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + crate::PhysicalFileIdentity::new(1, 1), + continuation_metadata(), + Arc::new(root_source.to_owned()), + ) + .unwrap(); + with_module + .add_explicit( + "/project/a.rue", + "/project/a.rue", + crate::PhysicalFileIdentity::new(5, 5), + continuation_metadata(), + Arc::new(module_source.to_owned()), + ) + .unwrap(); + let reads_with_module = with_module.accepted_read_manifest(); + let err = session + .publish_import_observation_batch(&frontier, &snapshot, reads_with_module, observations) + .unwrap_err() + .to_string(); + assert!( + err.contains("must equal this step's authorized additions exactly"), + "{err}", ); - assert_eq!(record.work.declaration_index.build_invocations, 1); - assert_eq!(record.work.binding.bind_invocations, 0); - assert_eq!(record.work.binding.namespace_setup_invocations, 0); - assert_eq!(record.work.binding.declaration_resolution_invocations, 0); - assert_eq!(record.work.binding.declaration_resolution_failures, 0); - assert_eq!( - record.work.binding.body_readiness_finalization_invocations, - 0 + } + + /// A batch cannot SUBSTITUTE predecessor provenance: an accepted-read entry + /// for an existing module with altered physical identity is rejected at + /// publication. + #[test] + fn observation_batch_rejects_substituted_provenance() { + let error = tampered_batch_error(|ctx| { + crate::DiscoverySourceAssembler::new( + ctx.clone(), + "/project/main.rue", + "/project/main.rue", + // Same content, DIFFERENT physical identity: the module revision + // matches but its provenance record does not. + crate::PhysicalFileIdentity::new(7, 7), + continuation_metadata(), + Arc::new("fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }".to_owned()), + ) + .unwrap() + }); + assert!( + error.contains("mutates a predecessor accepted-read provenance"), + "{error}", ); - assert_eq!(record.work.manifest.build_invocations, 0); - assert_eq!(record.work.body_analysis.bodies_attempted, 0); } #[test] - fn body_traversal_coordinator_counters_populate_from_a_multi_body_compile() { + fn stable_no_filesystem_boundary_classifies_unsatisfied_toolchain_input_not_ice() { + // RUE-1112 C3: the stable no-filesystem `canonical_semantic` entry cannot + // acquire, so an unsatisfied trusted-toolchain demand for otherwise-valid + // source is a deterministic CONTRACT failure, never an ICE (E9000). let source = snapshot( &[( 1, "/p/main.rue", "main.rue", - "fn helper() -> i32 { 7 }\n\ - fn other() -> i32 { helper() }\n\ - fn main() -> i32 { helper() + other() }", + "fn main() -> i32 { let _ = @parse_i64(\"1\"); 0 }", )], 1, ); let mut session = CompilerSession::new(); session.update(&source).into_result().unwrap(); - session + let errors = session .canonical_semantic(&CompileOptions::default()) - .unwrap(); - - let record = session.work().semantic_records.last().unwrap(); - let body = &record.work.body_analysis; - // `main`, `helper`, and `other` are each reached and analyzed once, so - // the coordinator computes at least three body transactions and - // publishes a closure containing all three. + .unwrap_err(); + let error = errors.first().expect("unsatisfied toolchain input error"); assert!( - body.bodies_attempted >= 3, - "expected at least three attempted bodies, got {}", - body.bodies_attempted + matches!( + error.kind, + rue_error::ErrorKind::UnsatisfiedTrustedToolchainInput(_) + ), + "expected an unsatisfied-trusted-toolchain-input classification, got {:?}", + error.kind ); - assert!( - body.closure_bodies_visited >= 3, - "expected at least three closure bodies, got {}", - body.closure_bodies_visited + assert_ne!(error.kind.code(), rue_error::ErrorCode::INTERNAL_ERROR); + assert_eq!( + error.kind.code(), + rue_error::ErrorCode::UNSATISFIED_TRUSTED_TOOLCHAIN_INPUT ); - // This program has no anonymous producers and no specializations, so - // the traversal completes in one pass at depth zero. - assert_eq!(body.closure_restarts, 0); - assert_eq!(body.deferred_producer_retries, 0); - assert_eq!(body.max_specialization_depth, 0); } #[test] - fn authoritative_key_mismatch_retains_failed_token_validation_work() { + fn provider_observation_counters_record_exact_production_work() { let source = snapshot( &[( 1, "/p/main.rue", "main.rue", - "fn helper() -> i32 { 0 } fn main() -> i32 { helper() }", + "fn helper(x: i32) -> i32 { x + 1 } fn main() -> i32 { helper(2) }", )], 1, ); let mut session = CompilerSession::new(); session.update(&source).into_result().unwrap(); - crate::canonical_semantic::with_test_authoritative_key_mismatch(|| { - session - .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - }); - - let record = session.work().semantic_records.last().unwrap(); - assert_eq!( - record.failure.unwrap().phase, - CanonicalSemanticFailurePhase::Declaration - ); - assert_eq!(record.work.body_owner_tokens.provisional_slots, 2); - assert_eq!(record.work.body_owner_tokens.authoritative_slots, 1); - assert_eq!(record.work.body_owner_tokens.slots_validated, 1); - assert_eq!(record.work.body_owner_tokens.tokens_installed, 0); - assert_eq!(record.work.body_owner_tokens.validation_failures, 1); - } - - #[test] - fn body_query_stage_failure_surfaces_as_diagnostic_not_panic() { - let source = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - // A deterministic per-body stage failure must reach the caller as a real - // compiler diagnostic rather than a disguised abort that panics the - // uncanceled session. - let _injection = session - .queries - .revisioned - .inject_body_transaction_failure_for_test(); - let errors = session + session .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - let rendered = errors.to_string(); - assert!( - rendered.contains("injected_body_transaction_failure"), - "expected the failing stage name in diagnostics, got: {rendered}" - ); - assert!( - rendered.contains("body instance"), - "expected the failing body instance in diagnostics, got: {rendered}" - ); - let record = session.work().semantic_records.last().unwrap(); + .expect("the program analyzes"); + let metrics = crate::unstable::provider_observation_metrics(&session); + assert_eq!(metrics.name_lookups, 3); + assert_eq!(metrics.import_lookups, 0); + assert_eq!(metrics.method_candidates, 0); + assert_eq!(metrics.operator_candidates, 0); + assert_eq!(metrics.declaration_facts, 25); + assert!(metrics.identity_facts > 0, "{metrics:?}"); + assert!(metrics.signature_facts > 0, "{metrics:?}"); + assert!(metrics.materializations > 0, "{metrics:?}"); assert_eq!( - record.failure.unwrap().phase, - CanonicalSemanticFailurePhase::BodyAnalysis + metrics.declaration_facts, + metrics.identity_facts + + metrics.signature_facts + + metrics.type_facts + + metrics.const_facts, + "the declaration aggregate must be exactly partitioned by real fact families" ); + assert_eq!(metrics.anonymous_facts, 0); + assert_eq!(metrics.producer_facts, 0); + assert_eq!(metrics.toolchain_facts, 4); } #[test] - fn cfg_failure_retains_work_without_replacing_the_last_good_baseline() { - let valid = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); - let changed = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }")], + fn published_lookup_root_lease_retains_production_body_lookups() { + // Production body analysis publishes its exact lookup-name terminals + // into the session lease. The lease owns retention independently from + // the request-scoped provider that observed those terminals. + let source = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn helper(x: i32) -> i32 { x + 1 } fn main() -> i32 { helper(2) }", + )], 1, ); - let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); - let baseline = session.canonical_semantic(&options).unwrap(); + session.update(&source).into_result().unwrap(); + session + .canonical_semantic(&CompileOptions::default()) + .expect("the program analyzes"); - session.update(&changed).into_result().unwrap(); - crate::canonical_semantic::with_test_cfg_failure_injection(|| { - session.canonical_semantic(&options).unwrap_err(); - }); - let failed = session.work().semantic_records.last().unwrap(); + let metrics = crate::unstable::lookup_pressure_metrics(&session); + assert!(metrics.published_roots > 0, "{metrics:?}"); + assert!(metrics.leased_terminals > 0, "{metrics:?}"); + assert!(metrics.retained_logical_keys > 0, "{metrics:?}"); + assert!(metrics.retained_family_nodes > 0, "{metrics:?}"); + assert!(metrics.retained_family_terminals > 0, "{metrics:?}"); + assert_eq!( + metrics.protected_growth, 0, + "no lease supersession grew a family" + ); assert_eq!( - failed.failure.unwrap().phase, - CanonicalSemanticFailurePhase::CfgConstruction + metrics.evictions, 0, + "no lease supersession evicted a terminal" ); - assert_eq!(failed.work.cfg.functions_considered, 1); - assert_eq!(failed.work.cfg.optimization_attempts, 0); - - session.update(&valid).into_result().unwrap(); - let recovered = session.canonical_semantic(&options).unwrap(); + assert_eq!(metrics.rederivations_after_eviction, 0); + // Exact lookup collection runs through the same provider-backed body + // transaction as production analysis. assert!( - Arc::ptr_eq(&recovered, &baseline), - "restoring the exact source leaf must reinstate its retained terminal" + crate::unstable::provider_observation_metrics(&session).name_lookups > 0, + "production lookup terminals must be observed by the provider" ); } #[test] - fn token_preparation_error_recovery_publishes_only_failure_diagnostics() { - let source = snapshot( + fn successful_closure_retires_unreachable_and_deleted_body_lookup_roots() { + let first = snapshot( &[( 1, "/p/main.rue", "main.rue", - "const value: i32 = 1; const value: i32 = 2; fn main() {}", + "fn helper() -> i32 { 1 } fn main() -> i32 { helper() }", + )], + 1, + ); + let unreachable = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn helper() -> i32 { 1 } fn main() -> i32 { 0 }", )], 1, ); + let deleted = snapshot( + &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], + 1, + ); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let errors = session + session.update(&first).into_result().unwrap(); + session .canonical_semantic(&CompileOptions::default()) - .unwrap_err(); - assert!( - errors - .iter() - .all(|error| error.kind.code().to_string() != "E1400"), - "unexpected internal diagnostic: {errors:?}" - ); - assert_eq!(session.work().semantic_entries, 1); - assert_eq!(session.work().semantic_records.len(), 1); - assert!(session.work().semantic_records[0].failed); - let diagnostics = session.latest_diagnostics().unwrap(); - assert!(!diagnostics.is_success()); - assert!(diagnostics.warnings().is_empty()); - } - - #[test] - fn stable_definitions_are_lazy_reused_semantic_projections() { - fn assert_send_sync() {} - assert_send_sync::(); + .expect("initial closure analyzes"); + let initial = crate::unstable::lookup_pressure_metrics(&session); + assert_eq!(initial.published_roots, 2, "{initial:?}"); - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let ordinary_options = CompileOptions::default(); - let ordinary = session.canonical_semantic(&ordinary_options).unwrap(); - assert_eq!(session.work().definitions.executions, 0); - assert_eq!(session.work().definition_entries, 0); + session.update(&unreachable).into_result().unwrap(); + session + .canonical_semantic(&CompileOptions::default()) + .expect("unreachable successor analyzes"); + let after_unreachable = crate::unstable::lookup_pressure_metrics(&session); + assert_eq!( + after_unreachable.published_roots, 1, + "helper root must retire when it leaves the reached closure: {after_unreachable:?}" + ); - let id_options = CompileOptions { - linker: LinkerMode::System("ignored".to_string()), - opt_level: OptLevel::O1, - ..ordinary_options.clone() - }; - let first = session.stable_definitions(&id_options).unwrap(); - let second = session - .stable_definitions(&CompileOptions { - linker: LinkerMode::Internal, - opt_level: OptLevel::O3, - ..ordinary_options - }) - .unwrap(); - assert!(Arc::ptr_eq(&first, &second)); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().definitions.executions, 1); - assert_eq!(session.work().definitions.reuses, 1); - assert_eq!(session.work().definition_entries, 1); - let record = &session.work().definition_records[0]; - assert_eq!(ordinary.work().binding.bind_invocations, 1); - assert_eq!(record.binding.bind_invocations, 1); - assert_eq!(record.manifest.build_invocations, 1); - assert_eq!(first.manifest_work().build_invocations, 1); - assert!(record.issuance.ids_issued > 0); - assert!(!record.failed); - - let published = first.clone(); - std::thread::spawn(move || assert!(!published.definitions().is_empty())) - .join() - .unwrap(); + session.update(&deleted).into_result().unwrap(); + session + .canonical_semantic(&CompileOptions::default()) + .expect("deleted successor analyzes"); + let after_deleted = crate::unstable::lookup_pressure_metrics(&session); + assert_eq!( + after_deleted.published_roots, 1, + "deleting an already-unreachable body cannot resurrect its root: {after_deleted:?}" + ); } #[test] - fn file_const_anonymous_types_use_epoch_local_comptime_producers() { - for source in [ - r#" -const T: type = struct { value: i32 }; -fn main() -> i32 { - let value: T = T { value: 42 }; - value.value -} -"#, - r#" -const T: type = enum { A, B(i32) }; -fn main() -> i32 { 0 } -"#, + fn nested_duplicate_parameter_diagnostics_rejoin_the_exact_current_occurrence() { + for source_text in [ + "struct S {\n fn m(self, a: i32, a: i32) {}\n}\nfn main() {}", + "struct S {\n fn make(a: i32, a: i32) {}\n}\nfn main() {}", ] { - let source = snapshot(&[(7, "/p/main.rue", "main.rue", source)], 7); + let duplicate_start = source_text + .rfind("a: i32") + .expect("fixture contains the duplicate parameter"); + let expected = rue_span::Span::with_file( + FileId::new(1), + duplicate_start as u32, + (duplicate_start + "a: i32".len()) as u32, + ); + let source = snapshot(&[(1, "/p/main.rue", "main.rue", source_text)], 1); let mut session = CompilerSession::new(); session.update(&source).into_result().unwrap(); - session + let errors = session .canonical_semantic(&CompileOptions::default()) - .unwrap(); - let definitions = session - .stable_definitions(&CompileOptions::default()) - .unwrap(); - assert!(definitions.definitions().iter().any(|record| { - record.stable_key().kind() == StableDefinitionKind::ValueConst - && record.stable_key().name() == "T" - })); + .unwrap_err(); + let error = errors.first().expect("duplicate parameter diagnostic"); + assert!( + error.to_string().contains("duplicate parameter name 'a'"), + "unexpected diagnostic: {error}" + ); + assert_eq!(error.span(), Some(expected)); } } #[test] - fn query_owned_declaration_shells_match_independent_air_and_token_oracles() { - let source = snapshot( - &[( - 7, - "/p/main.rue", - "main.rue", - r#" -pub struct Box { - fn get(mut self, comptime T: type) -> i32 { 0 } - fn inspect(borrow self) -> i32 { 1 } - fn make() -> Box { Box {} } -} -enum Choice { A, B(i32) } -const selected = 1; -const alias = selected; -drop fn Box(self) {} -unchecked fn main(value: i32) -> i32 { value } -extern "C" { fn getpid() -> i32; } -"#, - )], - 7, - ); - let options = CompileOptions::default(); + fn anonymous_comptime_producer_failure_is_a_deterministic_diagnostic() { + let source = SourceSnapshot::single( + "main.rue", + "fn empty() -> type {\n struct { }\n}\nfn main() -> i32 {\n let E = empty();\n 0\n}", + ) + .unwrap(); let mut session = CompilerSession::new(); session.update(&source).into_result().unwrap(); - let imports = session.accepted_semantic_import_graph().unwrap(); - let rir = session.canonical_rir().unwrap(); - let merged = session - .queries - .rir - .selected_record(&session.queries.graph) - .and_then(|entry| entry.merged.clone()) - .unwrap(); - let revision = session.queries.revisioned.current_parse_revision().unwrap(); - let query_shells = session - .queries - .revisioned - .projected_declaration_shells( - revision, - merged.ast(), - rue_query::CancellationToken::new(), - ) - .unwrap(); - let legacy = - legacy_air_declaration_shell_oracle(&merged, &rir, options.preview_features.clone()) - .unwrap(); - let expected_fingerprints = independently_expected_signature_fingerprints(&merged); - for shell in &query_shells { - assert_eq!( - shell.signature_fingerprint, expected_fingerprints[&shell.declaration_span], - "query fingerprint differs from independent AST/token partition" - ); - } - let mut comparable_query_shells = query_shells.clone(); - for shell in &mut comparable_query_shells { - shell.signature_fingerprint = [0; 32]; - } - sort_shell_comparison_algebra(&mut comparable_query_shells); - assert_eq!(legacy, comparable_query_shells); - - let mut foreign_locator = query_shells; - let first = &mut foreign_locator[0]; - first.declaration_span.start += 1; - let locator_error = crate::bound_definitions::configure_canonical_sema( - &merged, - &rir, - options.preview_features, - options.target, - &imports, - ) - .unwrap() - .predeclare_imported_declaration_shells(&foreign_locator) - .err() - .unwrap(); - assert!(matches!( - locator_error.first().map(|error| &error.kind), - Some(ErrorKind::InternalError(message)) - if message.contains("current RIR locator") - )); + let first = session + .canonical_semantic(&CompileOptions::default()) + .expect_err("empty anonymous struct is rejected"); + let second = session + .canonical_semantic(&CompileOptions::default()) + .expect_err("the retained producer failure remains deterministic"); + + assert_eq!(first, second); + assert_eq!(first.len(), 1, "unexpected diagnostics: {first:?}"); + let diagnostic = first.first().unwrap(); + assert!( + matches!(&diagnostic.kind, ErrorKind::EmptyStruct), + "producer failure must remain a source diagnostic, not request cancellation: {diagnostic:?}" + ); } #[test] - fn declaration_shell_oracles_cover_import_alias_trusted_and_multimodule_inputs() { - let root = FileId::new(1); - let standard = FileId::new(2); - let metadata = SourceMetadata::new_with_trusted_standard_library( - root, - HashMap::from([ - (root, "/project/main.rue".to_owned()), - (standard, "/project/std/lib.rue".to_owned()), - ]), - HashMap::from([ - (root, "main.rue".to_owned()), - (standard, "\0rue-std/lib.rue".to_owned()), - ]), - HashSet::from([standard]), - ) - .unwrap(); - let source = SourceSnapshot::new( - metadata, - vec![ - ( - root, - Arc::new( - r#" -pub const direct = @import("std/lib.rue"); -pub const alias = direct; -fn main() -> i32 { 0 } -"# - .to_owned(), - ), - ), - ( - standard, - Arc::new("pub struct Library {} pub const value: i32 = 1;".to_owned()), - ), - ], - ) - .unwrap(); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - let rir = session.canonical_rir().unwrap(); - let merged = session - .queries - .rir - .selected_record(&session.queries.graph) - .and_then(|entry| entry.merged.clone()) - .unwrap(); - let revision = session.queries.revisioned.current_parse_revision().unwrap(); - let query = session - .queries - .revisioned - .projected_declaration_shells( - revision, - merged.ast(), - rue_query::CancellationToken::new(), - ) - .unwrap(); - let legacy = - legacy_air_declaration_shell_oracle(&merged, &rir, options.preview_features.clone()) - .unwrap(); - let expected_fingerprints = independently_expected_signature_fingerprints(&merged); - let mut comparable = query.clone(); - for shell in &mut comparable { - assert_eq!( - shell.signature_fingerprint, - expected_fingerprints[&shell.declaration_span] + fn keyed_destructor_validity_preserves_production_diagnostics_and_spans() { + for (source_text, marker, message) in [ + ( + "drop fn Missing(self) {}\nfn main() {}", + "drop fn Missing(self) {}", + "unknown type 'Missing' in destructor", + ), + ( + "struct S {}\ndrop fn S(self) {}\ndrop fn S(self) {}\nfn main() {}", + "drop fn S(self) {}", + "duplicate destructor for type 'S'", + ), + ] { + let declaration_start = source_text + .rfind(marker) + .expect("fixture contains the rejected destructor"); + let expected = rue_span::Span::with_file( + FileId::new(1), + declaration_start as u32, + (declaration_start + marker.len()) as u32, ); - shell.signature_fingerprint = [0; 32]; - } - sort_shell_comparison_algebra(&mut comparable); - assert_eq!(legacy, comparable); - assert!( - query - .iter() - .any(|shell| shell.identity.is_trusted_standard_library) - ); - session.canonical_semantic(&options).unwrap(); - let definitions = session.stable_definitions(&options).unwrap(); - for name in ["direct", "alias"] { - let record = definitions - .definitions() - .iter() - .find(|record| { - record.stable_key().module().as_str() == "main.rue" - && record.stable_key().name() == name - && record.stable_key().kind() == StableDefinitionKind::ModuleBinding - }) - .unwrap_or_else(|| panic!("missing public module-binding definition for {name}")); - assert_eq!( - record.visibility(), - Some(rue_parser::ast::Visibility::Public) + let source = snapshot(&[(1, "/p/main.rue", "main.rue", source_text)], 1); + let mut session = CompilerSession::new(); + session.update(&source).into_result().unwrap(); + let errors = session + .canonical_semantic(&CompileOptions::default()) + .unwrap_err(); + let error = errors.first().expect("destructor validity diagnostic"); + assert!( + error.to_string().contains(message), + "unexpected diagnostic: {error}" ); + assert_eq!(error.span(), Some(expected)); } } - #[test] - fn query_and_retired_air_paths_reject_the_same_declaration_failures() { - for text in [ - "const value: i32 = 1; const value: i32 = 2; fn main() {}", - "struct Value {} drop fn Value(self) {} drop fn Value(self) {} fn main() {}", - "drop fn Missing(self) {} fn main() {}", - ] { - let source = snapshot(&[(1, "/main.rue", "main.rue", text)], 1); - let stages = crate::test_support::test_frontend_stages(&source).unwrap(); - let _merged = &stages.merged; - let rir = &stages.rir; - let retired = match rue_air::Sema::new_synthetic( - rir.rir(), - rir.semantic_symbols().interner(), - PreviewFeatures::new(), - ) - .bind_declarations_for_test() - { - Err(errors) => errors, - Ok(_) => panic!("retired AIR path unexpectedly accepted failure fixture"), - }; - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let query = match session.canonical_semantic(&CompileOptions::default()) { - Err(errors) => errors, - Ok(_) => panic!("query path unexpectedly accepted failure fixture"), - }; - let messages = - |errors: CompileErrors| errors.iter().map(ToString::to_string).collect::>(); - assert_eq!(messages(retired), messages(query)); - } + /// Generate `QUERY_TERMINAL_RETENTION_LIMIT + 1` distinct `CompileOptions` + /// for terminal retention/eviction tests (one more key than the store can + /// hold, forcing exactly one eviction). The low `feature_bits` bits of each + /// mask pick a preview-feature subset; the remaining high bits index the + /// compile target, so every mask is a distinct `(preview_features, target)` + /// pair. Both dimensions are part of the semantic, definition, and + /// dependency-manifest query keys — unlike `opt_level`, which keys only the + /// semantic store — so the variants force one eviction in every terminal + /// family under test. + /// + /// With `2` preview features the mask spans four subsets, and three targets + /// carry them to `4 * 3 = 12` distinct keys, exactly `LIMIT + 1`. The + /// assertion guards that the mask range spans no more `feature_bits`-wide + /// blocks than there are targets; if a future feature-count drop breaks it, + /// `QUERY_TERMINAL_RETENTION_LIMIT` must fall so `LIMIT + 1` still fits the + /// available `(preview_features, target)` key space. + #[allow(dead_code)] + fn retention_variants() -> Vec { + let feature_bits = PreviewFeature::all().len(); + let targets = Target::all(); + assert!( + (QUERY_TERMINAL_RETENTION_LIMIT >> feature_bits) < targets.len(), + "retention variants need a distinct (preview_features, target) key per mask" + ); + (0..=QUERY_TERMINAL_RETENTION_LIMIT) + .map(|mask| CompileOptions { + preview_features: PreviewFeature::all() + .iter() + .enumerate() + .filter(|(bit, _)| mask & (1 << bit) != 0) + .map(|(_, feature)| *feature) + .collect(), + target: targets[mask >> feature_bits], + ..CompileOptions::default() + }) + .collect() } #[test] - fn production_query_semantics_match_the_independent_retired_air_oracle() { - let source = snapshot( + fn nested_layout_change_invalidates_only_layout_consumers() { + let first = snapshot( &[( 1, - "/main.rue", + "/p/main.rue", "main.rue", - r#" -const base: i32 = 40; -const alias: i32 = base + 2; -fn increment(value: i32) -> i32 { value + 1 } -const callable = increment; -fn main() -> i32 { - callable(alias) -} -"#, + "struct Inner { a: i32 }\nstruct Outer { inner: Inner }\nfn consume(value: Outer) -> i32 { value.inner.a }\nfn unaffected() -> i32 { 7 }\nfn main() -> i32 { consume(Outer { inner: Inner { a: 1 } }) + unaffected() }", )], 1, ); - let options = CompileOptions::default(); + let second = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "struct Inner { a: i32, b: i32 }\nstruct Outer { inner: Inner }\nfn consume(value: Outer) -> i32 { value.inner.a }\nfn unaffected() -> i32 { 7 }\nfn main() -> i32 { consume(Outer { inner: Inner { a: 1, b: 2 } }) + unaffected() }", + )], + 1, + ); + let options = CompileOptions { + opt_level: OptLevel::O1, + ..CompileOptions::default() + }; let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let rir = session.canonical_rir().unwrap(); - let merged = session - .queries - .rir - .selected_record(&session.queries.graph) - .and_then(|entry| entry.merged.clone()) - .unwrap(); - let revision = session - .queries - .revisioned - .current_semantic_revision() - .unwrap(); - let production = session - .queries - .revisioned - .projected_declaration_semantics( - revision, - merged.ast(), - options.target, - &options.preview_features, - rue_query::CancellationToken::new(), - ) - .unwrap() - .declarations; - let definitions = session.stable_definitions(&options).unwrap(); - - // This producer is deliberately test-only and starts from a fresh AIR - // epoch that resolves declarations through the retired source-owned - // implementation. Production never evaluates this path or both paths. - let retired_bound = rue_air::Sema::new_synthetic( - rir.rir(), - rir.semantic_symbols().interner(), - options.preview_features.clone(), - ) - .bind_declarations_for_test() - .unwrap(); - let retired = retired_bound - .with_declaration_semantics(|exports, _| { - crate::durable_semantics::convert_declaration_semantics( - &merged, - &definitions, - exports, + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + let consume_key = body_query_key(&mut session, &options, "consume"); + let consume_transaction = retained_body_transaction(&session, &consume_key).2; + assert!( + consume_transaction.references().0.iter().any(|reference| { + matches!( + reference, + crate::body_query::BodyReference::Type(crate::TypeInstanceKey::Nominal( + crate::NominalInstanceKey::Named(definition) + )) if definition.name() == "Inner" ) - .unwrap_or_else(|failure| { - panic!( - "retired export conversion failed: {failure:?}; exports={exports:?}; definitions={:?}", - definitions - .definitions() - .iter() - .map(|record| record.stable_key()) - .collect::>() - ) - }) - }) - .unwrap(); - - assert_eq!(production, retired, "declaration producers diverged"); - } - - #[test] - fn stable_then_ordinary_reuses_the_validation_semantic_entry() { - let source = base(); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session.stable_definitions(&options).unwrap(); - let semantic_executions = session.work().semantic.executions; - let ordinary = session.canonical_semantic(&options).unwrap(); - - assert!(!ordinary.functions().is_empty()); - assert_eq!(semantic_executions, 1); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().semantic.reuses, 1); - assert_eq!(session.work().definitions.executions, 1); - assert_eq!( - session.work().definition_records[0] - .binding - .bind_invocations, - 1 + }), + "{consume_transaction:?}" ); - } - - #[test] - fn published_queries_support_stable_tooling_lookups() { - let source = base(); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - let published = session.update(&source).into_owner_result().unwrap(); - - let module_id = ModuleId::from_logical_path("a.rue").unwrap(); - let module = published.module(&module_id).expect("module by stable ID"); - assert_eq!(module.module_id(), &module_id); + let dependency_nodes = retained_body_dependency_nodes(&session, &consume_key); assert!( - published - .module(&ModuleId::from_logical_path("missing.rue").unwrap()) - .is_none() + dependency_nodes + .iter() + .any(|node| node.contains("signature") && node.contains("Inner")), + "{dependency_nodes:?}" + ); + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert_eq!(warm.work().cfg.cfg_reuses, 1); + assert_eq!(warm.work().cfg.cfg_builds_attempted, 2); + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&options).unwrap(); + assert_eq!( + format!("{:?}", warm.functions()), + format!("{:?}", fresh.functions()) ); - - let definitions = session.stable_definitions(&options).unwrap(); - let record = &definitions.definitions()[0]; - assert!(std::ptr::eq( - definitions - .definition_by_key(record.stable_key()) - .expect("definition by stable key"), - record - )); } #[test] - fn import_graph_requires_committed_discovery_for_import_bearing_revisions() { - let source = snapshot( - &[ - ( - 1, - "/p/app/main.rue", - "app/main.rue", - "fn main() -> i32 { let h = @import(\"helper.rue\"); 0 }", - ), - (2, "/p/app/helper.rue", "app/helper.rue", "fn helper() {}"), - ], + fn pointer_only_consumer_ignores_pointee_layout_but_field_consumer_rebuilds() { + let first = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "struct Foo { a: i32 }\nfn pointer_only(value: ptr const Foo) -> i32 { 7 }\nfn field(value: Foo) -> i32 { value.a }\nfn main() -> i32 { let value = Foo { a: 1 }; checked { pointer_only(@raw(value)) + field(value) } }", + )], + 1, + ); + let second = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "struct Foo { a: i32, b: i32 }\nfn pointer_only(value: ptr const Foo) -> i32 { 7 }\nfn field(value: Foo) -> i32 { value.a }\nfn main() -> i32 { let value = Foo { a: 1, b: 2 }; checked { pointer_only(@raw(value)) + field(value) } }", + )], 1, ); + let options = CompileOptions { + opt_level: OptLevel::O1, + ..CompileOptions::default() + }; let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let error = session.import_graph(None).unwrap_err(); - assert!( - error - .to_string() - .contains("require a committed discovery graph") + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert_eq!(warm.work().cfg.cfg_reuses, 1); + assert_eq!(warm.work().cfg.cfg_builds_attempted, 2); + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&options).unwrap(); + assert_eq!( + format!("{:?}", warm.functions()), + format!("{:?}", fresh.functions()) ); - assert_eq!(session.work().imports.executions, 0); } #[test] - fn committed_discovery_graph_is_consumed_without_resolution_fallback() { - let source = snapshot( - &[ - ( - 1, - "/p/main.rue", - "main.rue", - "fn main() -> i32 { let s = @import(\"helper.rue\"); 0 }", - ), - (2, "/p/helper.rue", "helper.rue", "fn helper() {}"), - ], + fn cfg_reuse_is_per_function_and_preserves_exact_build_work() { + let first = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn a() -> i32 { @dbg(\"same\"); @dbg(\"same\"); @dbg(\"alpha\"); 1 }\n\ + fn b() -> i32 { @dbg(\"beta\"); 2 }\n\ + fn main() -> i32 { @dbg(\"gamma\"); a() + b() }", + )], + 1, + ); + let second = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "// move every retained body and perturb another body's string projection\n\ + fn a() -> i32 { @dbg(\"same\"); @dbg(\"same\"); @dbg(\"alpha\"); 1 }\n\ + fn b() -> i32 { @dbg(\"delta\"); @dbg(\"beta\"); 3 }\n\ + fn main() -> i32 { @dbg(\"gamma\"); a() + b() }", + )], 1, ); + let options = CompileOptions { + opt_level: OptLevel::O1, + ..CompileOptions::default() + }; let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - assert!(session.import_graph(None).is_ok()); - assert_eq!(session.work().imports.executions, 0); - } + session.update(&first).into_result().unwrap(); + let cold = session.canonical_semantic(&options).unwrap(); + assert_eq!(cold.work().cfg.cfg_builds_attempted, 3); + assert_eq!(cold.work().cfg.optimization_attempts, 3); + let cold_atoms = cold + .functions() + .iter() + .flat_map(|function| function.local_atoms.iter()) + .collect::>(); + assert_eq!(cold_atoms.len(), 5); + assert_eq!( + cold_atoms + .iter() + .filter(|atom| atom.content.as_ref() == "same") + .count(), + 2 + ); + assert!(cold_atoms.iter().any(|atom| atom.dense_id > 1)); + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert_eq!( + warm.work().cfg.cfg_reuses, + 2, + "cfg work: {:?}", + warm.work().cfg + ); + assert_eq!(warm.work().cfg.cfg_import_successes, 2); + assert_eq!(warm.work().cfg.cfg_builds_attempted, 1); + assert_eq!(warm.work().cfg.optimization_attempts, 1); + assert_eq!(warm.work().cfg.optimized_level_attempts, 1); + for function in warm.functions() { + for atom in &function.local_atoms { + assert_eq!( + warm.strings() + .get(atom.dense_id as usize) + .map(String::as_str), + Some(atom.content.as_ref()) + ); + } + } - #[test] - fn empty_import_graph_is_send_sync_and_concurrently_readable() { - fn assert_send_sync() {} - assert_send_sync::(); - let mut session = CompilerSession::new(); - session.update(&base()).into_result().unwrap(); - let graph = session.import_graph(None).unwrap(); - assert!(graph.graph().records().is_empty()); - std::thread::spawn(move || assert!(graph.validation().is_valid())) - .join() - .unwrap(); + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&options).unwrap(); + assert_eq!( + format!("{:?}", warm.functions()), + format!("{:?}", fresh.functions()) + ); + assert_eq!( + format!("{:?}", warm.warnings()), + format!("{:?}", fresh.warnings()) + ); } #[test] - fn stable_definitions_prefers_a_successful_semantic_variant() { - let source = base(); + fn cfg_drop_facts_invalidate_same_layout_cleanup_changes() { + let first = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "struct Resource { value: i32 }\n\ + fn main() -> i32 { let resource = Resource { value: 1 }; 0 }", + )], + 1, + ); + let second = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "struct Resource { value: i32 }\n\ + drop fn Resource(self) { @dbg(self.value); }\n\ + fn main() -> i32 { let resource = Resource { value: 1 }; 0 }", + )], + 1, + ); let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); + session.update(&first).into_result().unwrap(); session.canonical_semantic(&options).unwrap(); - let imports = session.accepted_semantic_import_graph().unwrap(); - let successful_key = SemanticQueryKey { - input: CodegenInputDescriptor { - semantic: SemanticInputDescriptor::new( - &source, - options.target, - &options.preview_features, - ), - opt_level: options.opt_level.into(), - }, - imports, - }; - let successful = session.queries.semantic.get(&successful_key).unwrap(); - let mut failed_input = successful.key.input.clone(); - failed_input.opt_level = crate::StableOptLevel::O1; - let failed_imports = successful.key.imports.clone(); - let failed_errors = CompileErrors::from(CompileError::without_span( - ErrorKind::InvalidCompilerInput("synthetic prior failed opt variant".to_string()), - )); - let failed_source = session.published_snapshot.clone().unwrap(); - let failed_diagnostics = session.publish_diagnostics( - &failed_source, - FrontendDiagnosticIdentity::Semantic(semantic_diagnostic_input( - &failed_input, - failed_imports.clone(), - )), - Some(&failed_errors), - &[], + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert!( + warm.work().cfg.cfg_builds_attempted > 0, + "same-layout drop-fact changes must rebuild affected CFGs: {:?}", + warm.work().cfg ); - let failed_key = SemanticQueryKey { - input: failed_input, - imports: failed_imports, - }; - let source_dependency = session - .queries - .source_inputs - .selected(&session.queries.graph) - .unwrap(); - session.queries.semantic.insert_with_dependencies( - &mut session.queries.graph, - SemanticCacheEntry { - key: failed_key, - result: Err(failed_errors), - rir: None, - diagnostics: failed_diagnostics, - durable_declaration_cache: None, - oracle_injected: false, - }, - [source_dependency], + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&options).unwrap(); + assert_eq!( + normalize_session_local_spurs(format!("{:?}", warm.functions())), + normalize_session_local_spurs(format!("{:?}", fresh.functions())) ); - - let definitions = session - .stable_definitions(&CompileOptions { - opt_level: OptLevel::O2, - ..options - }) - .unwrap(); - - assert!(!definitions.definitions().is_empty()); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().definitions.executions, 1); - let record = &session.work().definition_records[0]; - assert_eq!(record.binding.bind_invocations, 1); - assert_eq!(record.manifest.build_invocations, 1); } #[test] - fn dependency_input_manifest_is_stable_ordered_and_adds_no_rir_scan() { - fn assert_send_sync() {} - assert_send_sync::(); - let source = base(); + fn cfg_relocation_covers_runtime_param_drop_and_nominal_field_domains() { + let program = |prefix: &str| { + format!( + "{prefix}\ + struct Leaf {{ value: i32 }}\n\ + drop fn Leaf(self) {{ @dbg(self.value); }}\n\ + struct Holder {{ leaf: Leaf }}\n\ + fn consume(value: Holder) -> i32 {{\n\ + @dbg(value.leaf.value);\n\ + value.leaf.value\n\ + }}\n\ + fn main() -> i32 {{ consume(Holder {{ leaf: Leaf {{ value: 7 }} }}) }}" + ) + }; + let first_text = program(""); + let second_text = program( + "struct Noise { pad: i64 }\n\ + fn noise(value: Noise) -> i64 { @assert(value.pad >= 0); value.pad }\n", + ); + let first = snapshot(&[(1, "/p/main.rue", "main.rue", first_text.as_str())], 1); + let second = snapshot(&[(1, "/p/main.rue", "main.rue", second_text.as_str())], 1); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let first = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let second = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - assert!(Arc::ptr_eq(&first, &second)); - assert!(first.definitions().windows(2).all(|pair| pair[0] < pair[1])); + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert_eq!( + warm.work().cfg.cfg_builds_attempted, + 0, + "live-domain relocation must not rebuild reusable unoptimized CFGs: {:?}", + warm.work().cfg + ); assert_eq!( - first.work().definition_records_visited, - first.definitions().len() + warm.work().cfg.cfg_reuses, + 5, + "every unchanged reachable CFG must be reused: {:?}", + warm.work().cfg ); - assert_eq!(first.work().extra_rir_instructions_visited, 0); - assert_eq!(session.work().dependency_manifests.executions, 1); - assert_eq!(session.work().dependency_manifests.reuses, 1); - assert_eq!(session.work().rir.executions, 1); - assert_eq!(session.work().semantic.executions, 1); - } - - #[test] - fn definition_fingerprints_ignore_relocation_file_ids_and_input_order() { - let first = snapshot( - &[ - (7, "/one/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/one/a.rue", "a.rue", "pub fn a() -> i32 { 1 }"), - ], - 7, + assert_eq!( + warm.work().cfg.optimization_attempts, + 0, + "complete relocation domains must reuse optimized terminals: {:?}", + warm.work().cfg ); - let relocated = snapshot( - &[ - (41, "/else/a.rue", "a.rue", "pub fn a() -> i32 { 1 }"), - (99, "/else/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - ], - 99, + assert_eq!( + warm.work().cfg.cfg_import_successes, + warm.work().cfg.cfg_reuses, + "the collector must receive already-relocated optimized terminals: {:?}", + warm.work().cfg ); - let mut left = CompilerSession::new(); - left.update(&first).into_result().unwrap(); - let left = left - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let mut right = CompilerSession::new(); - right.update(&relocated).into_result().unwrap(); - let right = right - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&options).unwrap(); assert_eq!( - left.definition_fingerprints(), - right.definition_fingerprints() + normalize_session_local_spurs(format!("{:?}", warm.functions())), + normalize_session_local_spurs(format!("{:?}", fresh.functions())) ); - assert!( - left.definition_fingerprints() - .iter() - .all(|fingerprint| fingerprint.schema_version == DEFINITION_FINGERPRINT_SCHEMA_V2) + assert_eq!( + format!("{:?}", warm.warnings()), + format!("{:?}", fresh.warnings()) ); } #[test] - fn definition_fingerprints_partition_function_signature_and_body_changes() { - fn fingerprints(source: &str) -> StableDefinitionInputFingerprint { - let source = snapshot(&[(7, "/p/main.rue", "main.rue", source)], 7); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() - .definition_fingerprints() - .iter() - .find(|fingerprint| fingerprint.key.name() == "value") - .expect("value definition fingerprint") - .clone() - } - - let original = fingerprints("fn value() -> i32 { 0 } fn main() { value(); }"); - let body_changed = fingerprints("fn value() -> i32 { 1 } fn main() { value(); }"); - assert_eq!(original.key, body_changed.key); - assert_eq!(original.declaration, body_changed.declaration); - assert_eq!(original.signature, body_changed.signature); - assert_ne!( - original.body_or_initializer, - body_changed.body_or_initializer + fn cfg_import_failure_rebuilds_current_body_with_exact_fallback_work() { + let first = snapshot( + &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 7 }")], + 1, ); - assert_eq!( - original.precision, - StableDefinitionFingerprintPrecision::SignatureAndBody + let second = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "// relocate the retained body\nfn main() -> i32 { 7 }", + )], + 1, ); + let options = CompileOptions { + opt_level: OptLevel::O1, + ..CompileOptions::default() + }; + let mut session = CompilerSession::new(); + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); - let visibility_changed = fingerprints("pub fn value() -> i32 { 0 } fn main() { value(); }"); - assert_eq!(original.key, visibility_changed.key); - assert_ne!(original.declaration, visibility_changed.declaration); - assert_ne!(original.signature, visibility_changed.signature); - assert_eq!( - original.body_or_initializer, - visibility_changed.body_or_initializer - ); + session.update(&second).into_result().unwrap(); + let warm = crate::canonical_semantic::with_test_cfg_import_failure_injection(|| { + session.canonical_semantic(&options).unwrap() + }); + assert_eq!(warm.work().cfg.cfg_reuse_candidates, 1); + assert_eq!(warm.work().cfg.cfg_reuses, 0); + assert_eq!(warm.work().cfg.cfg_import_attempts, 1); + assert_eq!(warm.work().cfg.cfg_import_successes, 0); + assert_eq!(warm.work().cfg.cfg_import_failures, 1); + assert_eq!(warm.work().cfg.cfg_fallbacks, 1); + assert_eq!(warm.work().cfg.cfg_builds_attempted, 1); + assert_eq!(warm.work().cfg.cfg_builds_succeeded, 1); + assert_eq!(warm.work().cfg.cfg_builds_failed, 0); + assert_eq!(warm.work().cfg.optimization_attempts, 1); + assert_eq!(warm.work().cfg.optimization_completions, 1); + assert_eq!(warm.work().cfg.optimized_level_attempts, 1); - let signature_changed = fingerprints("fn value() -> i64 { 0 } fn main() { value(); }"); - assert_eq!(original.declaration, signature_changed.declaration); - assert_ne!(original.signature, signature_changed.signature); + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&options).unwrap(); assert_eq!( - original.body_or_initializer, - signature_changed.body_or_initializer + normalize_session_local_spurs(format!("{:?}", warm.functions())), + normalize_session_local_spurs(format!("{:?}", fresh.functions())) ); } #[test] - fn definition_fingerprints_partition_all_authoritative_named_payloads() { - fn fingerprint( - source: &str, - name: &str, - kind: StableDefinitionKind, - ) -> StableDefinitionInputFingerprint { - let source = snapshot(&[(7, "/p/main.rue", "main.rue", source)], 7); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - manifest - .definition_fingerprints() + fn reused_parse_runtime_symbol_relocates_to_the_current_interner() { + let program = |prefix: &str| { + format!( + "{prefix}\ + const opt = @import(\"std/option.rue\");\n\ + fn parse_runtime() -> i32 {{ let _ = @parse_i64(\"7\"); 0 }}\n\ + fn main() -> i32 {{ parse_runtime() }}" + ) + }; + let first_text = program(""); + let second_text = + program("struct Noise { value: i64 }\nfn noise(value: Noise) -> i64 { value.value }\n"); + let first = well_known_option_isolation_snapshot(&first_text); + let second = well_known_option_isolation_snapshot(&second_text); + let options = CompileOptions::default(); + let runtime_symbols = |output: &crate::CanonicalSemanticOutput| { + output + .functions() .iter() - .find(|value| value.key.name() == name && value.key.kind() == kind) - .unwrap_or_else(|| { - panic!( - "missing {name:?} {kind:?}; got {:?}", - manifest - .definition_fingerprints() - .iter() - .map(|value| (value.key.name(), value.key.kind())) - .collect::>() - ) + .flat_map(|function| { + let cfg = &function.cfg; + cfg.blocks() + .iter() + .flat_map(|block| block.insts.iter()) + .filter_map(|value| match cfg.get_inst(*value).data { + rue_cfg::CfgInstData::Intrinsic { + runtime: Some(rue_air::RuntimeCallKind::ParseI64), + name, + .. + } => Some(name), + _ => None, + }) + .collect::>() }) - .clone() - } - fn assert_only_payload_changed( - before: &StableDefinitionInputFingerprint, - after: &StableDefinitionInputFingerprint, - precision: StableDefinitionFingerprintPrecision, - ) { - assert_eq!(before.key, after.key); - assert_eq!(before.declaration, after.declaration); - assert_eq!(before.signature, after.signature); - assert_ne!(before.body_or_initializer, after.body_or_initializer); - assert_eq!(before.precision, precision); - assert_eq!(after.precision, precision); - } - - let constant = fingerprint( - "const answer: i32 = 1; fn main() -> i32 { answer }", - "answer", - StableDefinitionKind::ValueConst, - ); - let constant_changed = fingerprint( - "const answer: i32 = 2; fn main() -> i32 { answer }", - "answer", - StableDefinitionKind::ValueConst, - ); - assert_only_payload_changed( - &constant, - &constant_changed, - StableDefinitionFingerprintPrecision::SignatureAndInitializer, - ); - - let method = fingerprint( - "struct S { n: i32, fn get(self) -> i32 { self.n } fn make() -> S { S { n: 1 } } } fn main() -> i32 { S.make().get() }", - "get", - StableDefinitionKind::Method, - ); - let method_changed = fingerprint( - "struct S { n: i32, fn get(self) -> i32 { self.n + 1 } fn make() -> S { S { n: 1 } } } fn main() -> i32 { S.make().get() }", - "get", - StableDefinitionKind::Method, - ); - assert_only_payload_changed( - &method, - &method_changed, - StableDefinitionFingerprintPrecision::SignatureAndBody, - ); - let method_owner = fingerprint( - "struct S { n: i32, fn get(self) -> i32 { self.n } fn make() -> S { S { n: 1 } } } fn main() -> i32 { S.make().get() }", - "S", - StableDefinitionKind::Struct, - ); - let method_owner_after_body_edit = fingerprint( - "struct S { n: i32, fn get(self) -> i32 { self.n + 1 } fn make() -> S { S { n: 1 } } } fn main() -> i32 { S.make().get() }", - "S", - StableDefinitionKind::Struct, - ); - assert_eq!(method_owner, method_owner_after_body_edit); + .collect::>() + }; - let comptime_function = fingerprint( - "fn id(comptime value: i32) -> i32 { value } fn main() -> i32 { id(1) }", - "id", - StableDefinitionKind::Function, - ); - let runtime_function = fingerprint( - "fn id(value: i32) -> i32 { value } fn main() -> i32 { id(1) }", - "id", - StableDefinitionKind::Function, - ); - assert_eq!(comptime_function.declaration, runtime_function.declaration); - assert_ne!(comptime_function.signature, runtime_function.signature); + let mut session = CompilerSession::new(); + publish_with_test_imports(&mut session, &first); + session.canonical_semantic(&options).unwrap(); + publish_with_test_imports(&mut session, &second); + let warm = session.canonical_semantic(&options).unwrap(); + assert_eq!(warm.work().cfg.cfg_builds_attempted, 0); + assert!(warm.work().cfg.cfg_reuses >= 2, "{:?}", warm.work().cfg); assert_eq!( - comptime_function.body_or_initializer, - runtime_function.body_or_initializer - ); - - let destructor = fingerprint( - "struct S { n: i32 } drop fn S(self) {} fn main() -> i32 { let s = S { n: 1 }; 0 }", - "S", - StableDefinitionKind::Destructor, - ); - let destructor_changed = fingerprint( - "fn cleanup() {} struct S { n: i32 } drop fn S(self) { cleanup(); } fn main() -> i32 { let s = S { n: 1 }; 0 }", - "S", - StableDefinitionKind::Destructor, - ); - assert_only_payload_changed( - &destructor, - &destructor_changed, - StableDefinitionFingerprintPrecision::SignatureAndBody, - ); - - let structure = fingerprint( - "struct S { n: i32 } fn main() -> i32 { 0 }", - "S", - StableDefinitionKind::Struct, - ); - let structure_changed = fingerprint( - "struct S { n: i64 } fn main() -> i32 { 0 }", - "S", - StableDefinitionKind::Struct, + warm.work().cfg.optimization_attempts, + 0, + "the collector must relocate the complete optimized terminal: {:?}", + warm.work().cfg ); + let warm_symbols = runtime_symbols(&warm); + assert_eq!(warm_symbols.len(), 1); + let warm_rir = warm.rir_owner(); assert_eq!( - structure.precision, - StableDefinitionFingerprintPrecision::ExactSignature + warm_rir + .semantic_symbols() + .interner() + .resolve(&warm_symbols[0]), + "parse_i64" ); - assert_ne!(structure.signature, structure_changed.signature); - assert_eq!(structure.body_or_initializer, None); - let enumeration = fingerprint( - "enum E { A(i32), B } fn main() -> i32 { 0 }", - "E", - StableDefinitionKind::Enum, - ); - let enumeration_changed = fingerprint( - "enum E { A(i64), B } fn main() -> i32 { 0 }", - "E", - StableDefinitionKind::Enum, + let mut fresh = CompilerSession::new(); + publish_with_test_imports(&mut fresh, &second); + let fresh_output = fresh.canonical_semantic(&options).unwrap(); + let fresh_symbols = runtime_symbols(&fresh_output); + assert_eq!(fresh_symbols.len(), 1); + let fresh_rir = fresh_output.rir_owner(); + assert_eq!( + fresh_rir + .semantic_symbols() + .interner() + .resolve(&fresh_symbols[0]), + "parse_i64" ); assert_eq!( - enumeration.precision, - StableDefinitionFingerprintPrecision::ExactSignature + normalize_session_local_spurs(format!("{:?}", warm.functions())), + normalize_session_local_spurs(format!("{:?}", fresh_output.functions())) ); - assert_ne!(enumeration.signature, enumeration_changed.signature); - assert_eq!(enumeration.body_or_initializer, None); - } - - fn synthetic_complete_manifest( - manifest: &SemanticDependencyInputManifest, - ) -> Arc { - let mut manifest = manifest.clone(); - manifest.dependency_graph_state = SemanticDependencyGraphState::from_blockers(Vec::new()); - manifest.definition_universe_state = SemanticDefinitionUniverseState::Complete; - Arc::new(manifest) } #[test] - fn dependency_completeness_states_require_evidence_and_preserve_projection() { - let complete = SemanticDependencyGraphState::from_blockers(Vec::new()); - assert!(complete.is_complete()); - assert!(complete.blockers().is_empty()); - assert!(complete.surface_complete(SemanticDependencySurface::FreeFunctionCall)); - - let blocker = SemanticDependencyBlocker { - owner: None, - surface: SemanticDependencySurface::FreeFunctionCall, - reason: SemanticDependencyIncompleteReason::CallerEndpointUnavailable, + fn reused_print_runtime_call_relocates_to_the_current_helper_symbol() { + let program = |prefix: &str| { + format!( + "{prefix}\ + fn probe_print() {{ println(\"literal\"); }}\n\ + fn main() -> i32 {{ probe_print(); 0 }}" + ) }; - let incomplete = - SemanticDependencyGraphState::from_blockers(vec![blocker.clone(), blocker.clone()]); - assert!(!incomplete.is_complete()); - assert_eq!(incomplete.blockers(), &[blocker]); - assert!(!incomplete.surface_complete(SemanticDependencySurface::FreeFunctionCall)); - assert!(incomplete.surface_complete(SemanticDependencySurface::NamedValueConst)); - } - - #[test] - fn every_incomplete_dependency_surface_forces_full_invalidation() { - let source = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 1, - ); + let runtime_calls = |output: &crate::CanonicalSemanticOutput| { + output + .functions() + .iter() + .flat_map(|function| { + let cfg = &function.cfg; + cfg.blocks() + .iter() + .flat_map(|block| block.insts.iter()) + .filter_map(|value| match cfg.get_inst(*value).data { + rue_cfg::CfgInstData::Call { + runtime: Some(runtime), + name, + .. + } => Some((runtime, name)), + _ => None, + }) + .collect::>() + }) + .collect::>() + }; + let first_text = program(""); + let second_text = program("fn noise() -> i32 { let interner_churn = 1; interner_churn }\n"); + let first = snapshot(&[(1, "/p/main.rue", "main.rue", &first_text)], 1); + let second = snapshot(&[(1, "/p/main.rue", "main.rue", &second_text)], 1); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let complete = synthetic_complete_manifest(&manifest); - - for (surface, reason) in [ - ( - SemanticDependencySurface::BodyOwner, - SemanticDependencyIncompleteReason::AnonymousBodyOwnerUnavailable, - ), - ( - SemanticDependencySurface::FreeFunctionCall, - SemanticDependencyIncompleteReason::CallerEndpointUnavailable, - ), - ( - SemanticDependencySurface::GenericNamedMethodCall, - SemanticDependencyIncompleteReason::GenericSubstitutionIdentityUnavailable, - ), - ( - SemanticDependencySurface::ImplicitNamedDestructor, - SemanticDependencyIncompleteReason::AnonymousDropOwnerUnavailable, - ), - ( - SemanticDependencySurface::DeclarationType, - SemanticDependencyIncompleteReason::ResolvedTypeIdentityUnavailable, - ), - ( - SemanticDependencySurface::DeclarationTypeCallHead, - SemanticDependencyIncompleteReason::TypeCallHeadIdentityUnavailable, - ), - ( - SemanticDependencySurface::SupportedTypeCallHead, - SemanticDependencyIncompleteReason::UnsupportedDynamicTypeCallHead, - ), - ( - SemanticDependencySurface::NamedValueConst, - SemanticDependencyIncompleteReason::ConstEndpointUnavailable, - ), - ] { - let blocker = SemanticDependencyBlocker { - owner: None, - surface, - reason, - }; - let mut incomplete = (*complete).clone(); - incomplete.dependency_graph_state = - SemanticDependencyGraphState::from_blockers(vec![blocker.clone()]); - let plan = plan_semantic_invalidation(&complete, &incomplete); + session.update(&first).into_result().unwrap(); + let cold = session.canonical_semantic(&options).unwrap(); + let cold_calls = runtime_calls(&cold); + assert_eq!(cold_calls.len(), 1); + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert_eq!(warm.work().cfg.cfg_builds_attempted, 0); + assert_eq!(warm.work().cfg.cfg_reuses, 2); + assert_eq!(warm.work().cfg.optimization_attempts, 0); + let warm_calls = runtime_calls(&warm); + assert_eq!(warm_calls.len(), 1); + assert_ne!( + cold_calls, warm_calls, + "the inserted declaration must perturb the live runtime-call symbol" + ); + let warm_rir = warm.rir_owner(); + for (runtime, symbol) in &warm_calls { assert_eq!( - plan.scope(), - &SemanticInvalidationScope::Full { - reasons: Arc::from([ - SemanticFullInvalidationReason::IncompleteDependencyGraph(Arc::from([ - blocker - ]),) - ]), - }, - "surface {surface:?} must fail closed" + warm_rir.semantic_symbols().interner().resolve(symbol), + runtime.helper().helper().symbol ); } - } - /// RUE-1209: the manifest no longer projects named-method or - /// named-destructor edges, so a method body's calls reach invalidation - /// only through the query-owned body references. Editing a callee must - /// still close over the calling method, the calling destructor, and their - /// own callers. - #[test] - fn incremental_invalidation_closes_across_method_and_destructor_bodies() { - let build = |leaf_value: i32| { - let source = format!( - "fn leaf() -> i32 {{ {leaf_value} }}\n\ - fn sink() {{}}\n\ - struct Value {{ fn run(borrow self) -> i32 {{ leaf() }} }}\n\ - drop fn Value(self) {{ sink(); }}\n\ - fn unaffected() -> i32 {{ 7 }}\n\ - fn main() -> i32 {{ let value = Value {{}}; value.run() }}" + let mut fresh = CompilerSession::new(); + fresh.update(&second).into_result().unwrap(); + let fresh_output = fresh.canonical_semantic(&options).unwrap(); + let fresh_calls = runtime_calls(&fresh_output); + assert_eq!(fresh_calls.len(), 1); + let fresh_rir = fresh_output.rir_owner(); + for (runtime, symbol) in &fresh_calls { + assert_eq!( + fresh_rir.semantic_symbols().interner().resolve(symbol), + runtime.helper().helper().symbol ); - let source = snapshot(&[(1, "/p/main.rue", "main.rue", source.as_str())], 1); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() - }; - let previous = build(1); - let current = build(2); - let plan = plan_semantic_invalidation(&previous, ¤t); - - assert_eq!(plan.scope(), &SemanticInvalidationScope::Incremental); + } assert_eq!( - plan.changed() - .iter() - .map(|key| key.name()) - .collect::>(), - ["leaf"] - ); - // `run` calls `leaf`, and `main` calls `run`. Both close transitively - // even though no named-method edge is projected. - let mut invalidated = plan - .invalidated() - .iter() - .map(|key| key.name()) - .collect::>(); - invalidated.sort_unstable(); - assert_eq!(invalidated, ["leaf", "main", "run"]); - assert!( - plan.reusable().iter().any(|key| key.name() == "unaffected"), - "an unrelated function stays reusable" + normalize_session_local_spurs(format!("{:?}", warm.functions())), + normalize_session_local_spurs(format!("{:?}", fresh_output.functions())) ); } #[test] - fn incremental_invalidation_closes_transitively_across_module_call_edges() { - let build = |leaf_value: i32| { - let main = r#" - const lib = @import("lib.rue"); - fn main() -> i32 { lib.middle() } - "#; - let lib = format!( - "pub fn leaf() -> i32 {{ {leaf_value} }}\n\ - pub fn middle() -> i32 {{ leaf() }}\n\ - pub fn unaffected() -> i32 {{ 7 }}" - ); - let source = snapshot( - &[ - (1, "/p/main.rue", "main.rue", main), - (2, "/p/lib.rue", "lib.rue", lib.as_str()), - ], + fn opt_level_only_change_reuses_cfg_and_recomputes_optimization_per_function() { + let source = snapshot( + &[( 1, - ); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() + "/p/main.rue", + "main.rue", + "fn left() -> i32 { 20 }\n\ + fn right() -> i32 { 22 }\n\ + fn main() -> i32 { left() + right() }", + )], + 1, + ); + let o0 = CompileOptions { + opt_level: OptLevel::O0, + ..CompileOptions::default() + }; + let o1 = CompileOptions { + opt_level: OptLevel::O1, + ..CompileOptions::default() }; - let previous = build(1); - let current = build(2); - let plan = plan_semantic_invalidation(&previous, ¤t); + let mut session = CompilerSession::new(); + session.update(&source).into_result().unwrap(); - assert_eq!(plan.scope(), &SemanticInvalidationScope::Incremental); - assert!(plan.added().is_empty()); - assert!(plan.removed().is_empty()); - assert_eq!( - plan.changed() - .iter() - .map(|key| key.name()) - .collect::>(), - ["leaf"] - ); - let mut expected = current - .definition_fingerprints - .iter() - .filter(|entry| matches!(entry.key.name(), "leaf" | "middle" | "main")) - .map(|entry| entry.key.clone()) - .collect::>(); - expected.sort(); - assert_eq!(plan.invalidated(), expected.as_slice()); - let mut reusable = current - .definition_fingerprints - .iter() - .filter(|entry| !expected.contains(&entry.key)) - .map(|entry| entry.key.clone()) - .collect::>(); - reusable.sort(); - assert_eq!(plan.reusable(), reusable.as_slice()); - assert_eq!(plan.work().reverse_closure_nodes_visited, 3); + let cold = session.canonical_semantic(&o0).unwrap(); + assert_eq!(cold.functions().len(), 3); + assert_eq!(cold.work().cfg.cfg_builds_attempted, 3); + assert_eq!(cold.work().cfg.optimization_attempts, 3); + + let optimized = session.canonical_semantic(&o1).unwrap(); + assert_eq!(optimized.functions().len(), 3); + assert_eq!(optimized.work().cfg.cfg_builds_attempted, 0); + assert_eq!(optimized.work().cfg.cfg_builds_succeeded, 0); + assert_eq!(optimized.work().cfg.cfg_builds_failed, 0); + assert_eq!(optimized.work().cfg.cfg_reuses, 3); + assert_eq!(optimized.work().cfg.cfg_import_attempts, 3); + assert_eq!(optimized.work().cfg.cfg_import_successes, 3); + assert_eq!(optimized.work().cfg.optimization_attempts, 3); + assert_eq!(optimized.work().cfg.optimization_completions, 3); + assert_eq!(optimized.work().cfg.optimized_level_attempts, 3); } #[test] - fn planner_ignores_relocation_but_rejects_global_semantic_input_changes() { - let original = snapshot( - &[ - (7, "/one/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/one/a.rue", "a.rue", "fn a() -> i32 { 1 }"), - ], - 7, + fn specialized_cfg_reuse_is_stable_across_unrelated_body_edits() { + let first = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn choose(comptime n: i32) -> i32 { n }\nfn b() -> i32 { 2 }\nfn main() -> i32 { choose(40) + b() }", + )], + 1, ); - let relocated = snapshot( - &[ - (41, "/else/a.rue", "a.rue", "fn a() -> i32 { 1 }"), - (99, "/else/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - ], - 99, + let second = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn choose(comptime n: i32) -> i32 { n }\nfn b() -> i32 { 3 }\nfn main() -> i32 { choose(40) + b() }", + )], + 1, ); - let build = |source: &SourceSnapshot, options: &CompileOptions| { - let mut session = CompilerSession::new(); - session.update(source).into_result().unwrap(); - session.semantic_dependency_inputs(options, None).unwrap() + let options = CompileOptions { + opt_level: OptLevel::O0, + ..CompileOptions::default() }; - let previous = build(&original, &CompileOptions::default()); - let moved = build(&relocated, &CompileOptions::default()); - let mut planner = CompilerSession::new(); - let plan = planner.semantic_invalidation_plan(&previous, &moved); - assert_eq!(plan.scope(), &SemanticInvalidationScope::Incremental); - assert!(plan.invalidated().is_empty()); - assert_eq!(plan.reusable().len(), 2); - - let alternative_target = *Target::all() - .iter() - .find(|&&target| target != moved.input().target) - .expect("at least one supported target differs from the current target"); - assert_ne!(alternative_target, moved.input().target); - let target = build( - &relocated, - &CompileOptions { - target: alternative_target, - ..CompileOptions::default() - }, - ); - assert!(matches!( - planner.semantic_invalidation_plan(&moved, &target).scope(), - SemanticInvalidationScope::Full { reasons } - if reasons.contains(&SemanticFullInvalidationReason::TargetChanged) - )); - let features = build( - &relocated, - &CompileOptions { - preview_features: PreviewFeatures::from([PreviewFeature::TestInfra]), - ..CompileOptions::default() - }, - ); - assert!(matches!( - planner.semantic_invalidation_plan(&moved, &features).scope(), - SemanticInvalidationScope::Full { reasons } - if reasons.contains(&SemanticFullInvalidationReason::PreviewFeaturesChanged) - )); - - let mut root_changed = (*moved).clone(); - root_changed.input.sources = SourceRevision::new( - ModuleId::from_logical_path("a.rue").unwrap(), - root_changed.input.sources.modules().to_vec(), - ) - .unwrap(); - let root_changed = Arc::new(root_changed); - assert!(matches!( - planner - .semantic_invalidation_plan(&moved, &root_changed) - .scope(), - SemanticInvalidationScope::Full { reasons } - if reasons.contains(&SemanticFullInvalidationReason::RootChanged) - )); - - let mut imports_changed = (*moved).clone(); - imports_changed.module_imports = vec![StableModuleImportDependency::Missing { - importer: ModuleId::from_logical_path("main.rue").unwrap(), - normalized_specifier: Arc::from("a.rue"), - }] - .into(); - let imports_changed = Arc::new(imports_changed); - let plan = planner.semantic_invalidation_plan(&moved, &imports_changed); - assert!(matches!( - plan.scope(), - SemanticInvalidationScope::Full { reasons } - if reasons.contains(&SemanticFullInvalidationReason::ModuleImportsChanged) - )); - assert!(plan.reusable().is_empty()); + let mut session = CompilerSession::new(); + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + session.update(&second).into_result().unwrap(); + let warm = session.canonical_semantic(&options).unwrap(); + assert!(warm.functions().iter().any(|function| { + matches!( + function.semantic_identity, + crate::FunctionInstanceKey::Specialization { .. } + ) + })); } #[test] - fn dependency_manifest_keeps_canonical_module_edges_across_physical_relocation() { - let original = snapshot( - &[ - ( - 1, - "/p/app/main.rue", - "app/main.rue", - "fn main() -> i32 { let h = @import(\"helper.rue\"); 0 }", - ), - (2, "/p/app/helper.rue", "app/helper.rue", "fn helper() {}"), - ], + fn cfg_reuse_rejects_target_and_callable_identity_changes() { + let first = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn old() -> i32 { 1 }\nfn stable() -> i32 { 2 }\nfn main() -> i32 { old() + stable() }", + )], 1, ); - let moved = snapshot( - &[ - ( - 1, - "/p/app/main.rue", - "app/main.rue", - "fn main() -> i32 { let h = @import(\"helper.rue\"); 0 }", - ), - (2, "/else/helper.rue", "app/helper.rue", "fn helper() {}"), - ], + let renamed = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn new() -> i32 { 1 }\nfn stable() -> i32 { 2 }\nfn main() -> i32 { new() + stable() }", + )], 1, ); + let host = Target::host().unwrap(); + let other = if host == Target::Aarch64Linux { + Target::X86_64Linux + } else { + Target::Aarch64Linux + }; + let first_options = CompileOptions { + target: host, + opt_level: OptLevel::O1, + ..CompileOptions::default() + }; + let other_options = CompileOptions { + target: other, + opt_level: OptLevel::O1, + ..CompileOptions::default() + }; let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &original); - let resolved = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - assert!(resolved.definition_universe_complete()); - assert!(matches!( - &resolved.module_imports()[0], - StableModuleImportDependency::Resolved { importer, target, .. } - if importer.as_str() == "app/main.rue" && target.as_str() == "app/helper.rue" - )); - - let work = publish_with_test_imports(&mut session, &moved); - assert_eq!(work.syntax.lexer_invocations, 0); - let relocated = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - assert!(relocated.definition_universe_complete()); - assert!(matches!( - &relocated.module_imports()[0], - StableModuleImportDependency::Resolved { importer, target, .. } - if importer.as_str() == "app/main.rue" && target.as_str() == "app/helper.rue" - )); - assert_eq!(relocated.work().import_records_visited, 1); - assert_eq!(relocated.work().extra_rir_instructions_visited, 0); - // Relocation moves only the canonical read provenance behind an - // unchanged requested path. Module identity, source text, and therefore - // the published revision are all untouched, so the manifest is reused - // rather than recomputed — the edge is stable without recomputing it. - assert_eq!(session.work().dependency_manifests.executions, 1); + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&first_options).unwrap(); + let cross_target = session.canonical_semantic(&other_options).unwrap(); + assert_eq!(cross_target.work().cfg.cfg_reuses, 0); + assert_eq!(cross_target.work().cfg.cfg_builds_attempted, 3); + session.update(&renamed).into_result().unwrap(); + let changed = session.canonical_semantic(&other_options).unwrap(); + assert_eq!(changed.work().cfg.cfg_reuses, 1); + assert_eq!(changed.work().cfg.cfg_builds_attempted, 2); + let mut fresh = CompilerSession::new(); + fresh.update(&renamed).into_result().unwrap(); + let fresh = fresh.canonical_semantic(&other_options).unwrap(); + assert_eq!( + format!("{:?}", changed.functions()), + format!("{:?}", fresh.functions()) + ); } #[test] - fn dependency_endpoint_translation_fails_closed_for_missing_and_non_functions() { - let source = snapshot( + fn value_constants_install_from_the_semantic_nucleus_without_fallback() { + let first = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "const n: i32 = 1; fn main() -> i32 { n }", + )], + 1, + ); + let second = snapshot( &[( 1, "/p/main.rue", "main.rue", - "const answer: i32 = 42; fn main() -> i32 { answer }", + "const n: i32 = 1; fn main() -> i32 { n + 1 }", )], 1, ); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let definitions = session - .stable_definitions(&CompileOptions::default()) - .unwrap(); - assert!(stable_free_function_endpoint(&definitions, 1, "missing").is_err()); - assert!(stable_free_function_endpoint(&definitions, 1, "answer").is_err()); - assert!(stable_named_method_endpoint(&definitions, 1, "answer", "answer").is_err()); + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + let main = body_query_key(&mut session, &options, "main"); + let (first_stamp, _, first_transaction) = retained_body_transaction(&session, &main); + session.update(&second).into_result().unwrap(); + let output = session.canonical_semantic(&options).unwrap(); + let (second_stamp, _, second_transaction) = retained_body_transaction(&session, &main); + assert_ne!(first_stamp, second_stamp); + assert!(matches!( + first_transaction, + crate::body_query::BodyTransaction::Success { .. } + )); + assert!(matches!( + second_transaction, + crate::body_query::BodyTransaction::Success { .. } + )); + assert_eq!(output.work().binding.declaration_resolution_invocations, 0); + assert_eq!(output.work().binding.durable_install_invocations, 1); + assert_eq!(output.work().declaration_reuse.durable_records_reused, 2); + } - let rejected = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { true }")], - 1, + fn assert_semantic_artifact_parity( + session: &CompilerSession, + actual: &CanonicalSemanticOutput, + fresh: &CanonicalSemanticOutput, + ) { + assert_eq!( + normalize_session_local_spurs(format!("{:?}", actual.functions())), + normalize_session_local_spurs(format!("{:?}", fresh.functions())) ); - session.update(&rejected).into_result().unwrap(); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - assert!(!manifest.definition_universe_complete()); - match &manifest.definition_universe_state { - SemanticDefinitionUniverseState::Complete => { - panic!("failed definition universe cannot be complete") - } - SemanticDefinitionUniverseState::Incomplete( - SemanticDefinitionUniverseIncompleteReason::StableDefinitionsFailed(failures), - ) => assert!(!failures.failures.is_empty()), + assert_eq!(actual.strings(), fresh.strings()); + assert_eq!( + format!("{:?}", actual.warnings()), + format!("{:?}", fresh.warnings()) + ); + let diagnostics = session + .latest_diagnostics() + .expect("semantic query publishes diagnostics"); + assert!(diagnostics.is_success()); + assert_eq!( + format!("{:?}", diagnostics.warnings()), + format!("{:?}", fresh.warnings()) + ); + } + + fn normalize_session_local_spurs(value: String) -> String { + let mut normalized = String::with_capacity(value.len()); + let mut rest = value.as_str(); + while let Some(start) = rest.find("Spur(") { + normalized.push_str(&rest[..start]); + normalized.push_str("Spur(_)"); + let after = &rest[start + "Spur(".len()..]; + let Some(end) = after.find(')') else { + normalized.push_str(after); + return normalized; + }; + rest = &after[end + 1..]; } - assert!(!manifest.free_function_caller_dependencies_complete()); - assert!(manifest.free_function_dependencies().is_empty()); + normalized.push_str(rest); + normalized + } + + fn assert_body_artifact_parity( + actual: &CanonicalSemanticOutput, + fresh: &CanonicalSemanticOutput, + ) { + assert_eq!( + format!("{:?}", actual.functions()), + format!("{:?}", fresh.functions()) + ); + assert_eq!(actual.strings(), fresh.strings()); + assert_eq!( + format!("{:?}", actual.warnings()), + format!("{:?}", fresh.warnings()) + ); + assert_eq!(actual.type_pool().stats(), fresh.type_pool().stats()); } - #[test] - fn stable_named_const_dependency_is_send_and_sync() { - fn assert_send_sync() {} - assert_send_sync::(); - assert_send_sync::(); - assert_send_sync::(); + fn assert_diagnostic_parity(actual: &CompilerSession, fresh: &CompilerSession) { + let actual = actual.latest_diagnostics().unwrap(); + let fresh = fresh.latest_diagnostics().unwrap(); + assert_eq!( + format!("{:?}", actual.stage()), + format!("{:?}", fresh.stage()) + ); + assert_eq!( + format!("{:?}", actual.errors()), + format!("{:?}", fresh.errors()) + ); + assert_eq!( + format!("{:?}", actual.warnings()), + format!("{:?}", fresh.warnings()) + ); } #[test] - fn implicit_drop_edges_distinguish_body_obligations_from_synthesized_glue() { - fn assert_send_sync() {} - assert_send_sync::(); - let program = r#" - struct Leaf { n: i32 } - drop fn Leaf(self) {} - struct Wrapper { leaves: [Leaf; 2] } - fn consume() { let value = Wrapper { leaves: [Leaf { n: 1 }, Leaf { n: 2 }] }; } - fn main() { consume(); } - "#; - let build = |id, physical: &str| { - let source = snapshot(&[(id, physical, "main.rue", program)], id); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() + fn composite_generic_signature_reuses_across_relocation_and_specialization_edit() { + let source = |file, physical: &str, value| { + snapshot( + &[( + file, + physical, + "main.rue", + &format!( + "fn first(comptime T: type, values: [[T; 2]; 2]) -> T {{ values[0][0] }} fn main() -> i32 {{ first(i32, [[1, 2], [3, {value}]]) }}" + ), + )], + file, + ) }; - let first = build(4, "/one/main.rue"); - let relocated = build(97, "/else/main.rue"); - assert_eq!( - first.implicit_named_destructor_dependencies(), - relocated.implicit_named_destructor_dependencies() - ); - let names = first - .implicit_named_destructor_dependencies() - .iter() - .map(|edge| { - ( - edge.source.kind(), - edge.source.name().to_string(), - edge.target.owner().unwrap().name().to_string(), - ) - }) - .collect::>(); - assert!(names.contains(&( - StableDefinitionKind::Function, - "consume".into(), - "Leaf".into(), - ))); - assert!(names.contains(&(StableDefinitionKind::Struct, "Leaf".into(), "Leaf".into(),))); - assert!(first.implicit_named_destructor_dependencies_complete()); + let first = source(1, "/old/main.rue", 4); + let relocated_edit = source(99, "/new/main.rue", 5); + let options = CompileOptions::default(); + let mut session = CompilerSession::new(); + session.update(&first).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + + session.update(&relocated_edit).into_result().unwrap(); + let reused = session.canonical_semantic(&options).unwrap(); + assert_eq!(reused.work().binding.declaration_resolution_invocations, 0); + assert_eq!(reused.work().binding.durable_payloads_installed, 2); + assert_eq!(reused.work().declaration_reuse.durable_records_reused, 2); assert_eq!( - first.work().implicit_named_destructor_events_translated, - first.implicit_named_destructor_dependencies().len() + reused.work().declaration_reuse.declaration_prefix_fallbacks, + 0 ); - assert_eq!(first.work().extra_rir_instructions_visited, 0); + + let mut fresh = CompilerSession::new(); + fresh.update(&relocated_edit).into_result().unwrap(); + let ordinary = fresh.canonical_semantic(&options).unwrap(); + assert_semantic_artifact_parity(&session, &reused, &ordinary); + assert_diagnostic_parity(&session, &fresh); } #[test] - fn new_specialization_drop_edge_matches_fresh_and_invalidates_on_destructor_edit() { - let program = |main_body: &str, destructor: Option| { - let destructor = destructor - .map(|value| format!("drop fn Leaf(self) {{ @dbg({value}); }}\n")) - .unwrap_or_default(); - format!( - "struct Leaf {{ n: i32 }}\n\ - {destructor}\ - fn consume(comptime T: type, value: T) -> i32 {{ 0 }}\n\ - fn main() -> i32 {{ {main_body} }}" + fn semantic_nucleus_installs_nested_generic_signatures_without_fallback() { + let source = |value| { + snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + &format!( + "fn first(comptime T: type, values: [[T; 2]; 2]) -> T {{ values[0][0] }} fn main() -> i32 {{ first(i32, [[1, 2], [3, {value}]]) }}" + ), + )], + 1, ) }; - let first_text = program("consume(i32, 1)", Some(1)); - let second_text = program("consume(Leaf, Leaf { n: 1 })", Some(1)); - let third_text = program("consume(Leaf, Leaf { n: 1 })", None); - let first = snapshot(&[(1, "/p/main.rue", "main.rue", &first_text)], 1); - let second = snapshot(&[(1, "/p/main.rue", "main.rue", &second_text)], 1); - let third = snapshot(&[(1, "/p/main.rue", "main.rue", &third_text)], 1); + let first = source(4); + let edited = source(5); let options = CompileOptions::default(); let mut session = CompilerSession::new(); session.update(&first).into_result().unwrap(); session.canonical_semantic(&options).unwrap(); - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - let specialization_edges = warm - .implicit_named_destructor_dependencies() - .iter() - .filter(|edge| { - matches!( - edge.source, - rue_air::ImplicitDropDependencySourceEvent::Specialization { .. } - ) && edge.target_owner_name == "Leaf" - }) - .collect::>(); + session.update(&edited).into_result().unwrap(); + let actual = session.canonical_semantic(&options).unwrap(); + assert_eq!(actual.work().binding.declaration_resolution_invocations, 0); + assert_eq!(actual.work().declaration_reuse.install_invocations, 1); + assert_eq!(actual.work().declaration_reuse.durable_records_reused, 2); + assert_eq!(actual.work().declaration_reuse.fallbacks, 0); assert_eq!( - specialization_edges.len(), - 1, - "the newly demanded specialization must publish its exact Leaf drop edge: {:?}", - warm.implicit_named_destructor_dependencies() + actual.work().declaration_reuse.declaration_prefix_fallbacks, + 0 + ); + assert_eq!( + actual.work().declaration_reuse.declaration_prefixes_built, + 1 ); let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let fresh_second = fresh.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", warm.implicit_named_destructor_dependencies()), - format!( - "{:?}", - fresh_second.implicit_named_destructor_dependencies() - ) + fresh.update(&edited).into_result().unwrap(); + let ordinary = fresh.canonical_semantic(&options).unwrap(); + assert_semantic_artifact_parity(&session, &actual, &ordinary); + assert_eq!(actual.type_pool().stats(), ordinary.type_pool().stats()); + assert_diagnostic_parity(&session, &fresh); + } + + #[test] + fn comptime_named_method_reuses_declarations_while_body_reuse_fails_closed() { + let source = |body: &str| snapshot(&[(1, "/p/main.rue", "main.rue", body)], 1); + let first = source( + "struct Value { fn choose(borrow self, comptime n: i32) -> i32 { n } } fn main() -> i32 { let value = Value {}; value.choose(1) }", + ); + let edited = source( + "struct Value { fn choose(borrow self, comptime n: i32) -> i32 { n + 1 } } fn main() -> i32 { let value = Value {}; value.choose(1) }", ); + let supported = source("fn main() -> i32 { 1 }"); + let supported_edit = source("fn main() -> i32 { 2 }"); + let options = CompileOptions::default(); + let mut session = CompilerSession::new(); + session.update(&first).into_result().unwrap(); + let cold = session.canonical_semantic(&options).unwrap(); + assert_eq!(cold.work().binding.declaration_resolution_invocations, 0); + assert_eq!(cold.work().binding.durable_install_invocations, 1); - session.update(&third).into_result().unwrap(); - let changed = session.canonical_semantic(&options).unwrap(); - assert!( - changed.work().body_analysis.specialized_bodies_attempted > 0, - "editing Leaf's destructor must invalidate consume(Leaf): {:?}", - changed.work().body_analysis + session.update(&edited).into_result().unwrap(); + let ordinary = session.canonical_semantic(&options).unwrap(); + assert_eq!( + ordinary.work().binding.declaration_resolution_invocations, + 0 ); - let mut fresh = CompilerSession::new(); - fresh.update(&third).into_result().unwrap(); - let fresh_third = fresh.canonical_semantic(&options).unwrap(); + assert_eq!(ordinary.work().binding.durable_install_invocations, 1); + assert_eq!(ordinary.work().binding.durable_payloads_installed, 3); + assert_eq!(ordinary.work().declaration_reuse.durable_records_reused, 3); assert_eq!( - normalize_session_local_spurs(format!("{:?}", changed.functions())), - normalize_session_local_spurs(format!("{:?}", fresh_third.functions())) + ordinary + .work() + .declaration_reuse + .declaration_prefix_fallbacks, + 0 ); + let mut fresh = CompilerSession::new(); + fresh.update(&edited).into_result().unwrap(); + let expected = fresh.canonical_semantic(&options).unwrap(); + assert_semantic_artifact_parity(&session, &ordinary, &expected); + + // Moving to a different declaration universe seeds a new baseline, and + // its next body edit can reuse normally. + session.update(&supported).into_result().unwrap(); + let seeded = session.canonical_semantic(&options).unwrap(); + assert_eq!(seeded.work().binding.declaration_resolution_invocations, 0); + assert_eq!(seeded.work().binding.durable_install_invocations, 1); + session.update(&supported_edit).into_result().unwrap(); + let recovered = session.canonical_semantic(&options).unwrap(); assert_eq!( - format!("{:?}", changed.implicit_named_destructor_dependencies()), - format!("{:?}", fresh_third.implicit_named_destructor_dependencies()) + recovered.work().binding.declaration_resolution_invocations, + 0 ); + assert_eq!(recovered.work().binding.durable_payloads_installed, 1); } #[test] - fn anonymous_drop_owner_composes_through_its_query_identity() { - let source = snapshot( + fn anonymous_structural_body_operations_export_durably_after_declaration_reuse() { + let first = snapshot( &[( - 4, + 1, "/p/main.rue", "main.rue", - r#" - fn Box(comptime T: type) -> type { - struct { v: T, drop fn(self) {} } - } - fn main() { let B = Box(i32); let value = B { v: 1 }; } - "#, + "fn Box(comptime T: type) -> type { struct { value: T, fn get(borrow self) -> T { self.value } } } fn main() -> i32 { let B = Box(i32); let value = B { value: 1 }; value.get() }", )], - 4, + 1, ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let output = session - .canonical_semantic(&CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() - }) - .unwrap(); - // Anonymous destructor ownership is represented by its query identity, - // so `main`, the type producer, and the reached destructor compose. - assert_eq!(output.functions().len(), 3); - } - - #[test] - fn resolved_declaration_type_edges_translate_without_rir_rescan() { - let source = snapshot( + let edited = snapshot( &[( 1, "/p/main.rue", "main.rue", - r#" - struct Leaf { n: i32 } - struct Holder { leaf: Leaf, fn get(borrow self, value: Leaf) -> Leaf { value } } - enum Choice { One(Leaf) } - fn convert(value: Leaf) -> Holder { Holder { leaf: value } } - drop fn Holder(self) {} - fn main() -> i32 { 0 } - "#, + "fn Box(comptime T: type) -> type { struct { value: T, fn get(borrow self) -> T { self.value } } } fn main() -> i32 { let B = Box(i32); let value = B { value: 2 }; value.get() }", )], 1, ); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let names = manifest - .declaration_type_dependencies() - .iter() - .map(|edge| { - ( - edge.source.name().to_owned(), - edge.target.name().to_owned(), - edge.kind, - ) - }) - .collect::>(); - assert!(names.contains(&( - "Holder".into(), - "Leaf".into(), - rue_air::DeclarationTypeDependencyKind::Field - ))); - assert!(names.contains(&( - "Choice".into(), - "Leaf".into(), - rue_air::DeclarationTypeDependencyKind::Payload - ))); - assert!(names.contains(&( - "convert".into(), - "Leaf".into(), - rue_air::DeclarationTypeDependencyKind::Signature - ))); - assert!(names.contains(&( - "get".into(), - "Leaf".into(), - rue_air::DeclarationTypeDependencyKind::Signature - ))); - assert!(names.contains(&( - "Holder".into(), - "Holder".into(), - rue_air::DeclarationTypeDependencyKind::Owner - ))); - assert!(manifest.declaration_type_dependencies_complete()); - assert_eq!(manifest.work().extra_rir_instructions_visited, 0); - } + session.update(&first).into_result().unwrap(); + let cold = session.canonical_semantic(&options).unwrap(); + assert_eq!(cold.work().binding.declaration_resolution_invocations, 0); + assert_eq!(cold.work().binding.durable_install_invocations, 1); - #[test] - fn deferred_nested_nominal_and_alias_types_keep_stable_declaration_edges() { - let build = |main_id, lib_id, root: &str, reversed: bool| { - let main = ( - main_id, - format!("{root}/main.rue"), - "main.rue", - r#"const lib = @import("lib.rue"); - const Alias = lib.Leaf; - fn consume(comptime N: i32, values: [Alias; N]) -> i32 { 0 } - fn main() -> i32 { 0 }"#, - ); - let lib = ( - lib_id, - format!("{root}/lib.rue"), - "lib.rue", - "pub struct Leaf { value: i32 }", - ); - let owned = if reversed { - vec![lib, main] - } else { - vec![main, lib] - }; - let entries = owned - .iter() - .map(|(id, path, module, text)| (*id, path.as_str(), *module, *text)) - .collect::>(); - let source = snapshot(&entries, main_id); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() - }; - let first = build(3, 8, "/p", false); - let moved = build(91, 4, "/elsewhere", true); + session.update(&edited).into_result().unwrap(); + let ordinary = session.canonical_semantic(&options).unwrap(); assert_eq!( - first.declaration_type_dependencies(), - moved.declaration_type_dependencies() - ); - let consume_targets = first - .declaration_type_dependencies() - .iter() - .filter(|edge| edge.source.name() == "consume") - .map(|edge| { - ( - edge.target.module().as_str(), - edge.target.name(), - edge.target.kind(), - ) - }) - .collect::>(); - assert!( - consume_targets.contains(&("main.rue", "Alias", StableDefinitionKind::ValueConst)), - "{consume_targets:#?}" + ordinary.work().binding.declaration_resolution_invocations, + 0 ); - assert!(consume_targets.contains(&("lib.rue", "Leaf", StableDefinitionKind::Struct))); - assert!(first.declaration_type_dependencies_complete()); - assert!( - !first - .dependency_blockers() - .iter() - .any(|blocker| { blocker.surface() == SemanticDependencySurface::DeclarationType }) + assert_eq!(ordinary.work().binding.durable_install_invocations, 1); + assert_eq!(ordinary.work().binding.durable_payloads_installed, 2); + assert_eq!(ordinary.work().declaration_reuse.durable_records_reused, 2); + assert_eq!( + ordinary + .work() + .declaration_reuse + .declaration_prefix_fallbacks, + 0 ); - assert_eq!(first.work().extra_rir_instructions_visited, 0); - } - - #[test] - fn module_qualified_type_call_head_uses_exact_callable_endpoint() { - let source = snapshot( - &[ - ( - 3, - "/p/main.rue", - "main.rue", - r#"const lib = @import("lib.rue"); - fn consume(comptime T: type, value: lib.Box(T)) -> i32 { 0 } - fn main() -> i32 { 0 }"#, - ), - ( - 8, - "/p/lib.rue", - "lib.rue", - "pub fn Box(comptime T: type) -> type { struct { value: T } }", - ), - ], - 3, + // Type producers are query inputs, not runtime function bodies. The + // reached executable set is `main` plus the anonymous `get` method. + assert_eq!(ordinary.functions().len(), 2); + let mut fresh = CompilerSession::new(); + fresh.update(&edited).into_result().unwrap(); + let expected = fresh.canonical_semantic(&options).unwrap(); + assert_semantic_artifact_parity(&session, &ordinary, &expected); + + let supported = snapshot( + &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 1 }")], + 1, ); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let [edge] = manifest.declaration_type_call_head_dependencies() else { - panic!("expected one module-qualified type-call head"); - }; - assert_eq!(edge.source.module().as_str(), "main.rue"); - assert_eq!(edge.source.name(), "consume"); - assert_eq!(edge.callable.module().as_str(), "lib.rue"); - assert_eq!(edge.callable.name(), "Box"); - assert_eq!(edge.callable.kind(), StableDefinitionKind::Function); + let supported_edit = snapshot( + &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 2 }")], + 1, + ); + session.update(&supported).into_result().unwrap(); + let seeded = session.canonical_semantic(&options).unwrap(); + assert_eq!(seeded.work().binding.declaration_resolution_invocations, 0); + assert_eq!(seeded.work().binding.durable_install_invocations, 1); + session.update(&supported_edit).into_result().unwrap(); + let recovered = session.canonical_semantic(&options).unwrap(); + assert_eq!( + recovered.work().binding.declaration_resolution_invocations, + 0 + ); + assert_eq!(recovered.work().binding.durable_payloads_installed, 1); } #[test] - fn fixed_string_type_head_is_a_builtin_input_not_a_definition() { - let source = snapshot( + fn signature_target_and_failed_body_changes_fail_closed_and_recovery_reuses() { + let base = snapshot( &[( - 4, + 1, "/p/main.rue", "main.rue", - "fn consume(value: Str(8)) -> i32 { 0 } fn main() -> i32 { 0 }", + "fn value() -> i32 { 1 } fn main() { value(); }", )], - 4, + 1, ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let manifest = session.semantic_dependency_inputs(&options, None).unwrap(); - let [input] = manifest.builtin_type_call_head_inputs() else { - panic!("expected one fixed-string builtin input"); - }; - assert_eq!(input.source.name(), "consume"); - assert_eq!( - input.builtin, - rue_air::BuiltinTypeCallHead::FixedCapacityString + let signature = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn value() -> i64 { 1 } fn main() { value(); }", + )], + 1, ); - assert!( - manifest - .declaration_type_call_head_dependencies() - .is_empty() + let broken_body = snapshot( + &[( + 1, + "/p/main.rue", + "main.rue", + "fn value() -> i64 { missing } fn main() { value(); }", + )], + 1, ); - assert!(manifest.supported_type_call_heads_complete()); - assert!(manifest.declaration_type_call_head_dependencies_complete()); - assert_eq!(manifest.work().builtin_type_call_head_inputs_translated, 1); - assert_eq!(manifest.work().extra_rir_instructions_visited, 0); - } - - #[test] - fn named_owner_associated_type_head_is_not_supported_type_syntax() { - let source = snapshot( + let recovered = snapshot( &[( - 6, + 1, "/p/main.rue", "main.rue", - r#"struct Factory { - fn Make() -> type { struct { value: i32 } } - } - fn consume(value: Factory.Make()) -> i32 { 0 } - fn main() -> i32 { 0 }"#, + "fn value() -> i64 { 2 } fn main() { value(); }", )], - 6, + 1, ); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - assert!( - session - .canonical_semantic(&CompileOptions::default()) - .is_err(), - "dotted type-call heads are module-qualified free functions, not associated functions" - ); - } + session.update(&base).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); - #[test] - fn named_const_initializer_edges_are_stable_direct_and_zero_scan() { - let program = r#" - struct Point { x: i32 } - fn inc(comptime n: i32) -> i32 { n + 1 } - const A: i32 = 1; - const B: i32 = inc(A); - const C: i32 = A + B; - const D: i32 = B + C; - const P = Point; - fn main() -> i32 { D } - "#; - let build = |file, path| { - let source = snapshot(&[(file, path, "main.rue", program)], file); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() - }; - let first = build(2, "/one/main.rue"); - let moved = build(88, "/moved/main.rue"); + session.update(&signature).into_result().unwrap(); + let changed = session.canonical_semantic(&options).unwrap(); + assert_eq!(changed.work().binding.declaration_resolution_invocations, 0); + + session.update(&broken_body).into_result().unwrap(); + assert!(session.canonical_semantic(&options).is_err()); + session.update(&recovered).into_result().unwrap(); + let recovered = session.canonical_semantic(&options).unwrap(); assert_eq!( - first.named_const_dependencies(), - moved.named_const_dependencies() + recovered.work().binding.declaration_resolution_invocations, + 0 ); - let names = first - .named_const_dependencies() + assert_eq!(recovered.work().declaration_reuse.durable_records_reused, 2); + + let mut other_target = options.clone(); + other_target.target = *Target::all() .iter() - .map(|edge| { - let target = match &edge.target { - StableNamedConstDependencyTarget::ValueConst(key) - | StableNamedConstDependencyTarget::FreeFunction(key) - | StableNamedConstDependencyTarget::NamedType(key) - | StableNamedConstDependencyTarget::ModuleBinding(key) => key.name(), - }; - (edge.source.name(), target) - }) - .collect::>(); - for edge in [ - ("B", "A"), - ("B", "inc"), - ("C", "A"), - ("C", "B"), - ("D", "B"), - ("D", "C"), - ("P", "Point"), - ] { - assert!( - names.contains(&edge), - "missing direct edge {edge:?}: {names:?}" - ); - } - assert!(first.named_value_const_dependencies_complete()); - assert_eq!(first.work().named_const_events_translated, names.len()); - assert_eq!(first.work().extra_rir_instructions_visited, 0); - - let renamed_program = program - .replace("const A: i32 = 1", "const Z: i32 = 1") - .replace("inc(A)", "inc(Z)") - .replace("A + B", "Z + B"); - let renamed_source = snapshot(&[(2, "/one/main.rue", "main.rue", &renamed_program)], 2); - let mut renamed_session = CompilerSession::new(); - renamed_session - .update(&renamed_source) - .into_result() - .unwrap(); - let renamed = renamed_session - .semantic_dependency_inputs(&CompileOptions::default(), None) + .find(|target| **target != options.target) .unwrap(); - assert_ne!( - first.named_const_dependencies(), - renamed.named_const_dependencies() + let target_changed = session.canonical_semantic(&other_target).unwrap(); + assert_eq!( + target_changed + .work() + .binding + .declaration_resolution_invocations, + 0 ); - assert!(renamed.named_const_dependencies().iter().any(|edge| { - edge.source.name() == "B" - && matches!(&edge.target, StableNamedConstDependencyTarget::ValueConst(key) if key.name() == "Z") - })); } #[test] - fn cyclic_const_initializers_publish_no_partial_dependency_graph() { - let source = snapshot( - &[( - 1, - "/p/main.rue", - "main.rue", - "const A: i32 = B; const B: i32 = A; fn main() -> i32 { 0 }", - )], + fn root_relocation_file_id_and_logical_changes_invalidate_correctly() { + let base = snapshot( + &[ + (1, "/old/a.rue", "a.rue", "fn a() {}"), + (2, "/old/b.rue", "b.rue", "fn b() {}"), + ], 1, ); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - assert!( - session - .canonical_semantic(&CompileOptions::default()) - .is_err() + let root_only = snapshot( + &[ + (1, "/old/a.rue", "a.rue", "fn a() {}"), + (2, "/old/b.rue", "b.rue", "fn b() {}"), + ], + 2, ); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - assert!(manifest.named_const_dependencies().is_empty()); - assert!(!manifest.named_value_const_dependencies_complete()); - assert!(!manifest.definition_universe_complete()); - } - - #[test] - fn qualified_const_edges_keep_module_binding_and_exact_member_identity() { - let source = snapshot( + let relocated = snapshot( &[ - ( - 3, - "/p/main.rue", - "main.rue", - r#"const lib = @import("lib.rue"); - const other = @import("other.rue"); - const X: i32 = lib.BASE; - const Y: i32 = other.BASE; - const T = lib.Row; - fn main() -> i32 { X + Y }"#, - ), - (11, "/p/other.rue", "other.rue", "pub const BASE: i32 = 5;"), - ( - 9, - "/p/lib.rue", - "lib.rue", - "pub const BASE: i32 = 4; pub struct Row { n: i32 }", - ), + (1, "/new/a.rue", "a.rue", "fn a() {}"), + (2, "/new/b.rue", "b.rue", "fn b() {}"), ], - 3, + 2, ); - let mut session = CompilerSession::new(); - publish_with_test_imports(&mut session, &source); - let manifest = session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let tags = manifest - .named_const_dependencies() - .iter() - .map(|edge| { - let (tag, target) = match &edge.target { - StableNamedConstDependencyTarget::ValueConst(key) => ("const", key), - StableNamedConstDependencyTarget::NamedType(key) => ("type", key), - StableNamedConstDependencyTarget::ModuleBinding(key) => ("module", key), - StableNamedConstDependencyTarget::FreeFunction(key) => ("fn", key), - }; - ( - edge.source.name(), - tag, - target.module().as_str(), - target.name(), - ) - }) - .collect::>(); - for expected in [ - ("X", "module", "main.rue", "lib"), - ("X", "const", "lib.rue", "BASE"), - ("T", "module", "main.rue", "lib"), - ("T", "type", "lib.rue", "Row"), - ("Y", "module", "main.rue", "other"), - ("Y", "const", "other.rue", "BASE"), - ] { - assert!(tags.contains(&expected), "missing {expected:?}: {tags:?}"); - } - assert!( - tags.iter() - .all(|(source, _, _, _)| *source != "lib" && *source != "other") + let reassigned = snapshot( + &[ + (11, "/new/a.rue", "a.rue", "fn a() {}"), + (12, "/new/b.rue", "b.rue", "fn b() {}"), + ], + 12, ); - } - - #[test] - fn const_dependency_capture_work_is_edge_proportional() { - let build = |extra: usize| { - let mut program = "const A: i32 = 1; const B: i32 = A;".to_string(); - for i in 0..extra { - program.push_str(&format!(" const UNUSED_{i}: i32 = {i};")); - } - program.push_str(" fn main() -> i32 { B }"); - let source = snapshot(&[(1, "/p/main.rue", "main.rue", &program)], 1); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap() - }; - let one = build(1); - let many = build(128); - assert_eq!( - one.named_const_dependencies(), - many.named_const_dependencies() + let renamed = snapshot( + &[ + (11, "/new/a2.rue", "a2.rue", "fn a() {}"), + (12, "/new/b.rue", "b.rue", "fn b() {}"), + ], + 12, ); - assert_eq!(one.work().named_const_events_translated, 1); - assert_eq!(many.work().named_const_events_translated, 1); - assert_eq!(many.work().extra_rir_instructions_visited, 0); - } - - #[test] - fn stable_definition_target_and_feature_inputs_are_separate() { - let source = base(); - let default = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session.stable_definitions(&default).unwrap(); - let other_target = *Target::all() - .iter() - .find(|&&target| target != default.target) - .expect("multiple compiler targets"); - session - .stable_definitions(&CompileOptions { - target: other_target, - ..default.clone() - }) - .unwrap(); - session - .stable_definitions(&CompileOptions { - preview_features: PreviewFeatures::from([PreviewFeature::TestInfra]), - ..default - }) - .unwrap(); + session.update(&base).into_result().unwrap(); + session.canonical_rir().unwrap(); - assert_eq!(session.work().definitions.executions, 3); - assert_eq!(session.work().definition_entries, 3); - assert_eq!(session.work().definition_records.len(), 3); - assert!(session.work().definition_records.iter().all(|record| { - record.binding.bind_invocations == 1 - && record.manifest.build_invocations == 1 - && !record.failed - })); + let root = session.update(&root_only); + assert!(root.downstream_invalidated()); + assert_eq!(root.work().modules_reused, 2); + root.into_result().unwrap(); + session.canonical_rir().unwrap(); + let moved = session.update(&relocated); + assert!(moved.downstream_invalidated()); + assert_eq!(moved.work().modules_rebound, 2); + moved.into_result().unwrap(); + session.canonical_rir().unwrap(); + let ids = session.update(&reassigned); + assert!(ids.downstream_invalidated()); + assert_eq!(ids.work().modules_reparsed, 0); + assert_eq!(ids.work().modules_rebound, 2); + ids.into_result().unwrap(); + session.canonical_rir().unwrap(); + let rename = session.update(&renamed); + assert!(rename.downstream_invalidated()); + assert_eq!(rename.invalidation().added.len(), 1); + assert_eq!(rename.invalidation().removed.len(), 1); + // ParseModule is keyed by stable logical module identity. A logical + // rename is a removed leaf plus a new demanded leaf, so its syntax is + // recomputed even when the source bytes happen to match. + assert_eq!(rename.work().modules_reparsed, 1); } #[test] - fn definition_store_eviction_recomputes_then_reuses_with_exact_metrics() { - let source = base(); + fn retained_body_failure_reprojects_spans_after_leading_trivia_edit() { + let first_text = "fn main() -> i32 { missing_name }"; + let shifted_text = "// newly inserted leading trivia\n\nfn main() -> i32 { missing_name }"; + let first = snapshot(&[(1, "/p/main.rue", "main.rue", first_text)], 1); + let shifted = snapshot(&[(1, "/p/main.rue", "main.rue", shifted_text)], 1); + let valid = snapshot( + &[(1, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], + 1, + ); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let variants = retention_variants(); - for options in &variants { - session.stable_definitions(options).unwrap(); - } - assert_eq!(session.work().definitions.executions, variants.len()); - assert_eq!(session.work().definitions.reuses, 0); + session.update(&valid).into_result().unwrap(); + session.canonical_semantic(&options).unwrap(); + let key = body_query_key(&mut session, &options, "main"); + session.update(&first).into_result().unwrap(); + let first_errors = session.canonical_semantic(&options).unwrap_err(); + let (first_stamp, _, _) = retained_body_transaction(&session, &key); + assert_eq!( + first_errors + .first() + .and_then(|error| error.span()) + .unwrap() + .start, + u32::try_from(first_text.find("missing_name").unwrap()).unwrap(), + ); + + session.update(&shifted).into_result().unwrap(); + let shifted_errors = session.canonical_semantic(&options).unwrap_err(); + let (shifted_stamp, _, _) = retained_body_transaction(&session, &key); assert_eq!( - session.work().retention.definition_query_entries, - QUERY_TERMINAL_RETENTION_LIMIT + shifted_stamp, first_stamp, + "a locator-only edit must reuse the semantic body transaction", ); - assert_eq!(session.work().retention.definition_query_evictions, 1); - - session.stable_definitions(&variants[0]).unwrap(); - assert_eq!(session.work().definitions.executions, variants.len() + 1); - assert_eq!(session.work().definitions.reuses, 0); - assert_eq!(session.work().retention.definition_query_evictions, 2); - - session.stable_definitions(&variants[0]).unwrap(); - assert_eq!(session.work().definitions.executions, variants.len() + 1); - assert_eq!(session.work().definitions.reuses, 1); - assert_eq!(session.work().retention.definition_query_evictions, 2); - } - - fn definition_query_fixture() -> (DefinitionQueryKey, DefinitionQueryOutput) { - let source = base(); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session.stable_definitions(&options).unwrap(); - let imports = session.accepted_semantic_import_graph().unwrap(); - let key = DefinitionQueryKey { - input: SemanticInputDescriptor::new(&source, options.target, &options.preview_features), - imports: imports.clone(), - }; - let output = session - .queries - .definitions - .get(&key) - .expect("definition computation was published") - .output - .clone(); - (key, output) - } - - #[test] - #[should_panic(expected = "typed query record key does not match")] - fn definition_store_rejects_foreign_option_provenance() { - let (original_key, output) = definition_query_fixture(); - let mut foreign_key = original_key; - foreign_key.input.preview_features = - StablePreviewFeatures::new(&PreviewFeatures::from([PreviewFeature::TestInfra])); - let mut store = TypedQueryStore::::default(); - store.insert(DefinitionCacheEntry { - key: foreign_key, - output, - }); - } - - #[test] - #[should_panic(expected = "typed query record key does not match")] - fn definition_store_rejects_foreign_import_provenance() { - let (original_key, output) = definition_query_fixture(); - let mut foreign_key = original_key; - foreign_key.imports = CanonicalImportGraph::from_discovery_records( - foreign_key.imports.root().clone(), - vec![crate::CanonicalImportRecord::new( - foreign_key.imports.root().clone(), - "foreign.rue", - CanonicalImportResolution::Missing, - )], + let shifted_span = shifted_errors + .first() + .and_then(|error| error.span()) + .unwrap(); + assert_eq!( + shifted_span.start, + u32::try_from(shifted_text.find("missing_name").unwrap()).unwrap(), ); - let mut store = TypedQueryStore::::default(); - store.insert(DefinitionCacheEntry { - key: foreign_key, - output, - }); + assert_eq!(shifted_span.file_id, crate::FileId::new(1)); } #[test] - fn definition_keys_ignore_opt_linker_relocation_file_ids_and_order() { - let original = snapshot( - &[ - (7, "/old/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - (2, "/old/a.rue", "a.rue", "fn a() {}"), - ], - 7, - ); - let moved = snapshot( - &[ - (90, "/new/a.rue", "a.rue", "fn a() {}"), - (40, "/new/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - ], - 40, - ); - let renamed = snapshot( - &[ - (90, "/new/lib/a.rue", "lib/a.rue", "fn a() {}"), - (40, "/new/main.rue", "main.rue", "fn main() -> i32 { 0 }"), - ], - 40, - ); + fn many_shallow_specializations_compile() { + // Regression (RUE-1083): breadth is not depth. A program may reach far + // more than `MAX_SPECIALIZATION_ROUNDS` distinct specializations as long + // as each sits at a shallow instantiation depth. Here `tag` has a + // compile-time-known base case, so every `tag(k)` is a leaf + // specialization at nesting depth 1; `main` reaches + // `MAX_SPECIALIZATION_ROUNDS + 8` of them. The retired total-count budget + // failed this program with E1200; the chain-depth budget compiles it. + let count = rue_air::specialize::MAX_SPECIALIZATION_ROUNDS + 8; + let mut body = String::from("fn main() -> i32 {\n let mut total = 0;\n"); + for k in 0..count { + body.push_str(&format!(" total = total + tag({k});\n")); + } + body.push_str(" total\n}\n"); + let program = format!("fn tag(comptime n: i32) -> i32 {{ n }}\n{body}"); + let valid = snapshot(&[(1, "/p/main.rue", "main.rue", program.as_str())], 1); let mut session = CompilerSession::new(); - session.update(&original).into_result().unwrap(); - let first = session - .stable_definitions(&CompileOptions { - linker: LinkerMode::System("x".to_string()), - opt_level: OptLevel::O2, - ..CompileOptions::default() - }) - .unwrap(); - let keys = |set: &BoundDefinitionSet| { - set.definitions() - .iter() - .map(|record| record.stable_key().clone()) - .collect::>() - }; - let first_keys = keys(&first); - - session.update(&moved).into_result().unwrap(); - let second = session - .stable_definitions(&CompileOptions::default()) - .unwrap(); - assert_eq!(keys(&second), first_keys); - assert_eq!(session.work().definition_entries_invalidated, 1); - - session.update(&renamed).into_result().unwrap(); - let third = session - .stable_definitions(&CompileOptions::default()) - .unwrap(); - assert_ne!(keys(&third), first_keys); + session.update(&valid).into_result().unwrap(); + session + .canonical_semantic(&CompileOptions::default()) + .expect("many shallow specializations must compile"); } #[test] - fn failed_parse_preserves_ids_while_semantic_rejection_issues_none() { - let valid = base(); - let syntax_bad = snapshot( - &[ - (7, "/p/main.rue", "main.rue", "fn main( {"), - (2, "/p/a.rue", "a.rue", "fn a() {}"), - ], - 7, - ); - let semantic_bad = snapshot( + fn cross_body_specialization_chain_still_overflows() { + // The chain-depth budget must still reject unbounded cross-body + // instantiation chains: `deepen` instantiates `deepen`, so + // each body publishes a strictly deeper specialization and the nesting + // depth grows without bound. This must fail with the same E1200 + // (`maximum nesting depth`) diagnostic as before. + let invalid = snapshot( &[( - 7, + 1, "/p/main.rue", "main.rue", - "fn main() -> i32 { missing_name }", + "fn deepen(comptime n: i32) -> i32 { deepen(n + 1) }\n\ + fn main() -> i32 { deepen(0) }", )], - 7, + 1, ); - let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); - let ids = session.stable_definitions(&options).unwrap(); - assert!(session.update(&syntax_bad).result().is_err()); - assert!(Arc::ptr_eq( - &ids, - &session.stable_definitions(&options).unwrap() - )); - - session.update(&semantic_bad).into_result().unwrap(); - let first = session.stable_definitions(&options).unwrap_err(); - let second = session.stable_definitions(&options).unwrap_err(); - assert_eq!(format!("{first:?}"), format!("{second:?}")); - assert_eq!(session.work().definitions.executions, 1); - // Current failure and the separately retained last-good definition - // artifact are both bounded typed terminals. - assert_eq!(session.work().definition_entries, 2); - assert_eq!(session.work().semantic_records.len(), 1); - assert!(session.work().semantic_records[0].failed); + session.update(&invalid).into_result().unwrap(); + let errors = session + .canonical_semantic(&CompileOptions::default()) + .unwrap_err(); + assert!( + matches!( + errors.first().map(|error| &error.kind), + Some(ErrorKind::ComptimeEvaluationFailed { reason }) + if reason.contains("maximum nesting depth") + ), + "runaway cross-body specialization chain must overflow with E1200" + ); + } - session.update(&valid).into_result().unwrap(); - assert!(session.stable_definitions(&options).is_ok()); - assert_eq!(session.work().definitions.executions, 1); - assert_eq!(session.work().definitions.reuses, 3); + #[test] + fn file_const_anonymous_types_use_epoch_local_comptime_producers() { + for source in [ + r#" +const T: type = struct { value: i32 }; +fn main() -> i32 { + let value: T = T { value: 42 }; + value.value +} +"#, + r#" +const T: type = enum { A, B(i32) }; +fn main() -> i32 { 0 } +"#, + ] { + let source = snapshot(&[(7, "/p/main.rue", "main.rue", source)], 7); + let mut session = CompilerSession::new(); + session.update(&source).into_result().unwrap(); + session + .canonical_semantic(&CompileOptions::default()) + .unwrap(); + let definitions = session + .stable_definitions(&CompileOptions::default()) + .unwrap(); + assert!(definitions.definitions().iter().any(|record| { + record.stable_key().kind() == StableDefinitionKind::ValueConst + && record.stable_key().name() == "T" + })); + } } #[test] - fn diagnostic_artifacts_retain_attempt_provenance_and_query_identity() { - fn assert_send_sync() {} - assert_send_sync::(); + fn query_and_retired_air_paths_reject_the_same_declaration_failures() { + for text in [ + "const value: i32 = 1; const value: i32 = 2; fn main() {}", + "struct Value {} drop fn Value(self) {} drop fn Value(self) {} fn main() {}", + "drop fn Missing(self) {} fn main() {}", + ] { + let source = snapshot(&[(1, "/main.rue", "main.rue", text)], 1); + let stages = crate::test_support::test_frontend_stages(&source).unwrap(); + let _merged = &stages.merged; + let rir = &stages.rir; + let retired = match rue_air::Sema::new_synthetic( + rir.rir(), + rir.semantic_symbols().interner(), + PreviewFeatures::new(), + ) + .bind_declarations_for_test() + { + Err(errors) => errors, + Ok(_) => panic!("retired AIR path unexpectedly accepted failure fixture"), + }; + let mut session = CompilerSession::new(); + session.update(&source).into_result().unwrap(); + let query = match session.canonical_semantic(&CompileOptions::default()) { + Err(errors) => errors, + Ok(_) => panic!("query path unexpectedly accepted failure fixture"), + }; + let messages = + |errors: CompileErrors| errors.iter().map(ToString::to_string).collect::>(); + assert_eq!(messages(retired), messages(query)); + } + } - let valid = base(); - let syntax_bad = snapshot(&[(7, "/attempt/bad.rue", "bad.rue", "fn main( {")], 7); - let semantic_bad = snapshot( - &[( - 7, - "/attempt/semantic.rue", - "semantic.rue", - "fn main() -> i32 { missing_name }", - )], - 7, - ); - let warning_source = snapshot( - &[( - 7, - "/attempt/warning.rue", - "warning.rue", - "fn main() -> i32 { let unused = 1; 0 }", - )], - 7, - ); + #[test] + fn published_queries_support_stable_tooling_lookups() { + let source = base(); + let options = CompileOptions::default(); let mut session = CompilerSession::new(); - session.update(&valid).into_result().unwrap(); - let published = session.published_owner().unwrap().clone(); - - let failed = session.update(&syntax_bad); - let syntax_diagnostics = failed.diagnostics().clone(); - assert_eq!( - syntax_diagnostics.source().metadata(), - syntax_bad.metadata() - ); - assert_eq!( - syntax_diagnostics.source_revision(), - syntax_bad.source_revision() - ); - assert!(!syntax_diagnostics.errors().is_empty()); - assert!(Arc::ptr_eq(session.published_owner().unwrap(), &published)); - assert!(Arc::ptr_eq( - session - .diagnostics_for(&syntax_bad, &FrontendDiagnosticIdentity::Syntax) - .unwrap(), - &syntax_diagnostics - )); + let published = session.update(&source).into_owner_result().unwrap(); - session.update(&semantic_bad).into_result().unwrap(); - let options = CompileOptions::default(); - session.canonical_semantic(&options).unwrap_err(); - let first = session.latest_diagnostics().unwrap().clone(); - let first_fingerprint = first - .errors() - .iter() - .map(ToString::to_string) - .collect::>(); - session.canonical_semantic(&options).unwrap_err(); - let reused = session.latest_diagnostics().unwrap().clone(); - assert!(Arc::ptr_eq(&first, &reused)); - assert_eq!( - first_fingerprint, - reused - .errors() - .iter() - .map(ToString::to_string) - .collect::>() + let module_id = ModuleId::from_logical_path("a.rue").unwrap(); + let module = published.module(&module_id).expect("module by stable ID"); + assert_eq!(module.module_id(), &module_id); + assert!( + published + .module(&ModuleId::from_logical_path("missing.rue").unwrap()) + .is_none() ); - let FrontendDiagnosticIdentity::Semantic(input) = first.identity() else { - panic!("semantic diagnostic stage"); - }; - assert_eq!(input.opt_level(), crate::StableOptLevel::O0); - session.update(&warning_source).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - let warning = session.latest_diagnostics().unwrap().clone(); - assert!(warning.is_success()); - assert!(!warning.warnings().is_empty()); - session - .canonical_semantic(&CompileOptions { - linker: LinkerMode::Internal, - ..options.clone() - }) - .unwrap(); - assert!(Arc::ptr_eq(&warning, session.latest_diagnostics().unwrap())); - session - .canonical_semantic(&CompileOptions { - opt_level: OptLevel::O1, - ..options.clone() - }) - .unwrap(); - let optimized = session.latest_diagnostics().unwrap().clone(); - assert!(!Arc::ptr_eq(&warning, &optimized)); - session - .canonical_semantic(&CompileOptions { - preview_features: PreviewFeatures::from([PreviewFeature::TestInfra]), - ..options.clone() - }) - .unwrap(); - let featured = session.latest_diagnostics().unwrap().clone(); - assert!(!Arc::ptr_eq(&warning, &featured)); - let other_target = *Target::all() - .iter() - .find(|&&target| target != options.target) - .unwrap(); - session - .canonical_semantic(&CompileOptions { - target: other_target, - ..options - }) - .unwrap(); - assert!(!Arc::ptr_eq( - &warning, - session.latest_diagnostics().unwrap() + let definitions = session.stable_definitions(&options).unwrap(); + let record = &definitions.definitions()[0]; + assert!(std::ptr::eq( + definitions + .definition_by_key(record.stable_key()) + .expect("definition by stable key"), + record )); - let old = syntax_diagnostics.clone(); - std::thread::spawn(move || { - assert_eq!(old.source().metadata().root_file_id(), FileId::new(7)); - assert!(!old.errors().is_empty()); - }) - .join() - .unwrap(); } #[test] - fn merge_diagnostics_are_memoized_pointer_identically() { - let duplicate = snapshot( - &[(1, "/p/main.rue", "main.rue", "fn main() {} fn main() {}")], + fn import_graph_requires_committed_discovery_for_import_bearing_revisions() { + let source = snapshot( + &[ + ( + 1, + "/p/app/main.rue", + "app/main.rue", + "fn main() -> i32 { let h = @import(\"helper.rue\"); 0 }", + ), + (2, "/p/app/helper.rue", "app/helper.rue", "fn helper() {}"), + ], + 1, + ); + let mut session = CompilerSession::new(); + session.update(&source).into_result().unwrap(); + let error = session.import_graph(None).unwrap_err(); + assert!( + error + .to_string() + .contains("require a committed discovery graph") + ); + assert_eq!(session.work().imports.executions, 0); + } + + #[test] + fn committed_discovery_graph_is_consumed_without_resolution_fallback() { + let source = snapshot( + &[ + ( + 1, + "/p/main.rue", + "main.rue", + "fn main() -> i32 { let s = @import(\"helper.rue\"); 0 }", + ), + (2, "/p/helper.rue", "helper.rue", "fn helper() {}"), + ], 1, ); let mut session = CompilerSession::new(); - session.update(&duplicate).into_result().unwrap(); - session.merge().unwrap_err(); - let first = session.latest_diagnostics().unwrap().clone(); - session.merge().unwrap_err(); - let second = session.latest_diagnostics().unwrap().clone(); - assert!(Arc::ptr_eq(&first, &second)); - assert!(matches!( - first.identity(), - FrontendDiagnosticIdentity::Merge - )); - assert_eq!(session.work().merge.executions, 1); - assert_eq!(session.work().diagnostic_reuses, 1); + publish_with_test_imports(&mut session, &source); + assert!(session.import_graph(None).is_ok()); + assert_eq!(session.work().imports.executions, 0); + } + + #[test] + fn empty_import_graph_is_send_sync_and_concurrently_readable() { + fn assert_send_sync() {} + assert_send_sync::(); + let mut session = CompilerSession::new(); + session.update(&base()).into_result().unwrap(); + let graph = session.import_graph(None).unwrap(); + assert!(graph.graph().records().is_empty()); + std::thread::spawn(move || assert!(graph.validation().is_valid())) + .join() + .unwrap(); } #[test] @@ -16100,38 +8469,6 @@ fn main() -> i32 { assert_eq!(next.work().modules_reparsed, 1); } - #[test] - fn merge_cache_reselects_its_origin_after_diagnostic_index_eviction() { - let source = base(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session.merge().unwrap(); - let origin = session.latest_diagnostics().unwrap().clone(); - - evict_diagnostic_index(&mut session); - assert!( - session - .most_recent_diagnostics_for(&source, &FrontendDiagnosticIdentity::Merge) - .is_none() - ); - let publications = session.work().diagnostic_publications; - let reuses = session.work().diagnostic_reuses; - - session.merge().unwrap(); - - assert!(Arc::ptr_eq(session.latest_diagnostics().unwrap(), &origin)); - assert_eq!(session.work().merge.executions, 1); - assert_eq!(session.work().merge.reuses, 1); - assert_eq!(session.work().diagnostic_publications, publications); - assert_eq!(session.work().diagnostic_reuses, reuses + 1); - assert!(Arc::ptr_eq( - session - .most_recent_diagnostics_for(&source, &FrontendDiagnosticIdentity::Merge) - .unwrap(), - &origin - )); - } - #[test] fn direct_import_cache_reselects_its_origin_after_diagnostic_index_eviction() { let source = base(); @@ -16153,7 +8490,7 @@ fn main() -> i32 { assert!(Arc::ptr_eq(&reused, &origin)); assert_eq!(session.work().import_diagnostics.executions, 1); assert_eq!(session.work().import_diagnostics.reuses, 1); - assert_eq!(session.work().retention.import_query_entries, 1); + assert_eq!(session.work().retention.import_query_entries, 0); assert_eq!(session.work().retention.import_query_evictions, 0); assert_eq!(session.work().diagnostic_publications, publications); assert!(Arc::ptr_eq( @@ -16164,330 +8501,6 @@ fn main() -> i32 { )); } - #[test] - fn semantic_cache_reselects_failure_origin_after_diagnostic_index_eviction() { - let source = snapshot( - &[(7, "/p/main.rue", "main.rue", "fn main() -> i32 { missing }")], - 7, - ); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - let first_errors = session.canonical_semantic(&options).unwrap_err(); - let origin = session.latest_diagnostics().unwrap().clone(); - let stage = origin.identity().clone(); - - evict_diagnostic_index(&mut session); - assert!( - session - .most_recent_diagnostics_for(&source, &stage) - .is_none() - ); - let publications = session.work().diagnostic_publications; - let reuses = session.work().diagnostic_reuses; - - let reused_errors = session.canonical_semantic(&options).unwrap_err(); - - assert_eq!( - first_errors - .iter() - .map(ToString::to_string) - .collect::>(), - reused_errors - .iter() - .map(ToString::to_string) - .collect::>() - ); - assert!(Arc::ptr_eq(session.latest_diagnostics().unwrap(), &origin)); - assert_eq!(session.work().semantic.executions, 1); - assert_eq!(session.work().semantic.reuses, 1); - assert_eq!(session.work().diagnostic_publications, publications); - assert_eq!(session.work().diagnostic_reuses, reuses + 1); - assert!(Arc::ptr_eq( - session - .most_recent_diagnostics_for(&source, &stage) - .unwrap(), - &origin - )); - } - - #[test] - fn semantic_diagnostic_identity_includes_the_accepted_import_graph() { - // Candidate precedence retargets the import, not a substituted graph: - // the importer-relative candidate outranks the project-root one, so - // adding a sibling `choice.rue` next to the importer moves - // `@import("choice.rue")` off the root-level module without touching a - // single spelling in the importer. - let main = r#"const selected = @import("choice.rue"); -fn main() -> i32 { selected.value() }"#; - let root_only = snapshot( - &[ - (1, "/p/app/main.rue", "app/main.rue", main), - ( - 2, - "/p/choice.rue", - "choice.rue", - "pub fn value() -> i32 { 1 }", - ), - ], - 1, - ); - let with_sibling = snapshot( - &[ - (1, "/p/app/main.rue", "app/main.rue", main), - ( - 2, - "/p/choice.rue", - "choice.rue", - "pub fn value() -> i32 { 1 }", - ), - ( - 3, - "/p/app/choice.rue", - "app/choice.rue", - "pub fn value() -> i32 { 2 }", - ), - ], - 1, - ); - let resolved_target = |graph: &CanonicalImportGraph| match graph.records()[0].resolution() { - CanonicalImportResolution::Resolved(module) => module.as_str().to_owned(), - other => panic!("expected a resolved import, got {other:?}"), - }; - - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - let graph_a = crate::test_support::TestDiscoveryHost::new(&root_only) - .unwrap() - .drive(&mut session) - .unwrap() - .graph; - assert_eq!(resolved_target(&graph_a), "choice.rue"); - let output_a = session.canonical_semantic(&options).unwrap(); - let diagnostics_a = session.latest_diagnostics().unwrap().clone(); - let main_key = body_query_key(&mut session, &options, "main"); - let main_a = retained_body_transaction(&session, &main_key).0; - - let graph_b = crate::test_support::TestDiscoveryHost::new(&with_sibling) - .unwrap() - .drive(&mut session) - .unwrap() - .graph; - assert_eq!(resolved_target(&graph_b), "app/choice.rue"); - let output_b = session.canonical_semantic(&options).unwrap(); - let diagnostics_b = session.latest_diagnostics().unwrap().clone(); - let main_b = retained_body_transaction(&session, &main_key).0; - - let mut fresh = CompilerSession::new(); - publish_with_test_imports(&mut fresh, &with_sibling); - let fresh_b = fresh.canonical_semantic(&options).unwrap(); - - assert!(!Arc::ptr_eq(&diagnostics_a, &diagnostics_b)); - assert_ne!( - main_a, main_b, - "accepted import retargeting invalidates the body terminal" - ); - assert_ne!( - normalize_session_local_spurs(format!("{:?}", output_a.functions())), - normalize_session_local_spurs(format!("{:?}", output_b.functions())) - ); - assert_semantic_artifact_parity(&session, &output_b, &fresh_b); - let FrontendDiagnosticIdentity::Semantic(input_a) = diagnostics_a.identity() else { - panic!("semantic diagnostics"); - }; - let FrontendDiagnosticIdentity::Semantic(input_b) = diagnostics_b.identity() else { - panic!("semantic diagnostics"); - }; - assert_eq!(input_a.program().imports(), &graph_a); - assert_eq!(input_b.program().imports(), &graph_b); - assert_eq!(session.work().semantic.executions, 2); - } - - #[test] - fn long_failure_recovery_sequence_bounds_diagnostics_and_preserves_last_good() { - let options = CompileOptions::default(); - let source = |text: &str| snapshot(&[(7, "/p/main.rue", "main.rue", text)], 7); - let initial = source("fn main() -> i32 { 0 }"); - let mut session = CompilerSession::new(); - session.update(&initial).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - assert_eq!(session.work().retention.diagnostic_entries, 5); - assert_eq!(session.work().retention.diagnostic_source_attempts, 1); - assert_eq!( - session.work().retention.diagnostic_source_bytes, - initial.files().map(|file| file.source.len()).sum::() - ); - let initial_good = session.last_good_semantic_diagnostics().unwrap().clone(); - assert!(initial_good.is_success()); - - let first_bad = source("fn main( {"); - let first_update = session.update(&first_bad); - assert!(first_update.result().is_err()); - let caller_pinned = first_update.diagnostics().clone(); - assert!(Arc::ptr_eq( - session.last_good_semantic_diagnostics().unwrap(), - &initial_good - )); - - let mut maximum_attempt_bytes: usize = initial.files().map(|file| file.source.len()).sum(); - for revision in 1..=32 { - let syntax_text = format!("// {}\nfn main( {{", "x".repeat(revision)); - maximum_attempt_bytes = maximum_attempt_bytes.max(syntax_text.len()); - let syntax_bad = source(&syntax_text); - assert!(session.update(&syntax_bad).result().is_err()); - let before_semantic_failure = session.last_good_semantic_diagnostics().unwrap().clone(); - - let semantic_text = format!("fn main() -> i32 {{ missing_{revision} }}"); - maximum_attempt_bytes = maximum_attempt_bytes.max(semantic_text.len()); - let semantic_bad = source(&semantic_text); - session.update(&semantic_bad).into_result().unwrap(); - session.canonical_semantic(&options).unwrap_err(); - assert!(Arc::ptr_eq( - session.last_good_semantic_diagnostics().unwrap(), - &before_semantic_failure - )); - assert!(!session.latest_diagnostics().unwrap().is_success()); - - let valid_text = format!("fn main() -> i32 {{ {revision} }}"); - maximum_attempt_bytes = maximum_attempt_bytes.max(valid_text.len()); - let valid = source(&valid_text); - session.update(&valid).into_result().unwrap(); - let recovered = session.canonical_semantic(&options).unwrap(); - let recovered_diagnostics = session.latest_diagnostics().unwrap(); - assert!(recovered_diagnostics.is_success()); - assert!(Arc::ptr_eq( - recovered_diagnostics, - session.latest_successful_diagnostics().unwrap() - )); - assert!(Arc::ptr_eq( - recovered_diagnostics, - session.last_good_semantic_diagnostics().unwrap() - )); - assert!(Arc::ptr_eq( - recovered_diagnostics, - session - .diagnostics_for( - &valid, - &FrontendDiagnosticIdentity::Semantic(semantic_diagnostic_input( - recovered.input(), - session.accepted_semantic_import_graph().unwrap(), - )) - ) - .unwrap() - )); - } - - let retention = session.work().retention; - assert!(retention.diagnostic_entries <= FRONTEND_DIAGNOSTIC_RETENTION_LIMIT); - assert!(retention.diagnostic_source_attempts <= retention.diagnostic_entries); - assert!( - retention.diagnostic_source_bytes - <= FRONTEND_DIAGNOSTIC_RETENTION_LIMIT * maximum_attempt_bytes - ); - assert!( - session - .diagnostics_for(&first_bad, &FrontendDiagnosticIdentity::Syntax) - .is_none(), - "unpinned old cache entry should be evicted" - ); - assert_eq!(caller_pinned.source_revision(), first_bad.source_revision()); - assert!(!caller_pinned.errors().is_empty()); - - let final_source = source("fn main() -> i32 { 32 }"); - let mut fresh = CompilerSession::new(); - fresh.update(&final_source).into_result().unwrap(); - let fresh_output = fresh.canonical_semantic(&options).unwrap(); - let retained_output = session.canonical_semantic(&options).unwrap(); - assert_eq!( - format!("{:?}", retained_output.functions()), - format!("{:?}", fresh_output.functions()) - ); - assert_eq!(retained_output.strings(), fresh_output.strings()); - } - - #[test] - fn invalidation_plan_keys_coalesce_structurally_equal_manifest_values() { - let source = snapshot( - &[(7, "/p/main.rue", "main.rue", "fn main() -> i32 { 0 }")], - 7, - ); - let mut builder = CompilerSession::new(); - builder.update(&source).into_result().unwrap(); - let base = builder - .semantic_dependency_inputs(&CompileOptions::default(), None) - .unwrap(); - let manifests = (0..=FRONTEND_INVALIDATION_PLAN_RETENTION_LIMIT + 3) - .map(|_| Arc::new((*base).clone())) - .collect::>(); - let mut planner = CompilerSession::new(); - let first = planner.semantic_invalidation_plan(&manifests[0], &manifests[1]); - let mut last = first.clone(); - for pair in manifests.windows(2).skip(1) { - last = planner.semantic_invalidation_plan(&pair[0], &pair[1]); - } - - assert_eq!(planner.work().retention.invalidation_plans, 1); - assert_eq!(planner.work().retention.dependency_manifests, 2); - let executions = planner.work().invalidation_plans.executions; - let recomputed = planner.semantic_invalidation_plan(&manifests[0], &manifests[1]); - assert!(Arc::ptr_eq(&first, &recomputed)); - assert_eq!(planner.work().invalidation_plans.executions, executions); - let reused = planner - .semantic_invalidation_plan(&manifests[manifests.len() - 2], manifests.last().unwrap()); - assert!(Arc::ptr_eq(&last, &reused)); - assert_eq!(planner.work().retention.invalidation_plans, 1); - assert_eq!(planner.work().retention.dependency_manifests, 2); - } - - #[test] - fn durable_ordinary_bodies_reuse_in_the_canonical_worklist_and_match_fresh_output() { - let source = snapshot( - &[( - 41, - "/relocated/main.rue", - "main.rue", - "fn helper(x: i32) -> i32 { let message: str = \"same\"; @dbg(message); x + 1 }\n\ - fn main() -> i32 { let message: str = \"same\"; @dbg(message); helper(41) }", - )], - 41, - ); - let mut session = CompilerSession::new(); - session.update(&source).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - - let optimized_options = CompileOptions { - opt_level: OptLevel::O1, - ..CompileOptions::default() - }; - let reused = session.canonical_semantic(&optimized_options).unwrap(); - assert_eq!( - reused - .functions() - .iter() - .map(|function| function.local_atoms.len()) - .sum::(), - 2 - ); - let mut fresh = CompilerSession::new(); - fresh.update(&source).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&optimized_options).unwrap(); - assert_eq!( - format!("{:?}", reused.functions()), - format!("{:?}", fresh.functions()) - ); - assert_eq!(reused.strings(), fresh.strings()); - assert_eq!( - format!("{:?}", reused.warnings()), - format!("{:?}", fresh.warnings()) - ); - assert_eq!( - format!("{:?}", reused.analyzed_body_owners()), - format!("{:?}", fresh.analyzed_body_owners()) - ); - } - #[test] fn specialized_reuse_survives_relocation_file_ids_and_input_order() { let original = snapshot( @@ -16802,97 +8815,31 @@ fn main() -> i32 { selected.value() }"#; fn main() -> i32 {\n\ let left = Resource { value: 20 };\n\ let right = Resource { value: 20 };\n\ - borrowed(1, borrow left) + owned(1, right)\n\ - }"; - let first = snapshot(&[(43, "/p/main.rue", "main.rue", first_text)], 43); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - - let changed_text = first_text.replace("cleanup();", "cleanup(); let marker = 0;"); - let changed_source = snapshot( - &[(43, "/p/main.rue", "main.rue", changed_text.as_str())], - 43, - ); - session.update(&changed_source).into_result().unwrap(); - let changed = session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - let mut fresh_session = CompilerSession::new(); - fresh_session.update(&changed_source).into_result().unwrap(); - let fresh = fresh_session - .canonical_semantic(&CompileOptions::default()) - .unwrap(); - assert_body_artifact_parity(&changed, &fresh); - assert_diagnostic_parity(&session, &fresh_session); - } - - #[test] - fn durable_ordinary_body_edit_reuses_unaffected_reachable_body_and_failure_keeps_baseline() { - let original = snapshot( - &[( - 51, - "/p/main.rue", - "main.rue", - "fn a() -> i32 { 1 }\nfn b() -> i32 { 2 }\nfn main() -> i32 { a() + b() }", - )], - 51, - ); - let edited = snapshot( - &[( - 51, - "/p/main.rue", - "main.rue", - "fn a() -> i32 { 3 }\nfn b() -> i32 { 2 }\nfn main() -> i32 { a() + b() }", - )], - 51, - ); + borrowed(1, borrow left) + owned(1, right)\n\ + }"; + let first = snapshot(&[(43, "/p/main.rue", "main.rue", first_text)], 43); let mut session = CompilerSession::new(); - session.update(&original).into_result().unwrap(); + session.update(&first).into_result().unwrap(); session - .semantic_dependency_inputs(&CompileOptions::default(), None) + .canonical_semantic(&CompileOptions::default()) .unwrap(); - session.update(&edited).into_result().unwrap(); - let edited_output = session + let changed_text = first_text.replace("cleanup();", "cleanup(); let marker = 0;"); + let changed_source = snapshot( + &[(43, "/p/main.rue", "main.rue", changed_text.as_str())], + 43, + ); + session.update(&changed_source).into_result().unwrap(); + let changed = session .canonical_semantic(&CompileOptions::default()) .unwrap(); - assert_eq!(edited_output.functions().len(), 3); - session - .semantic_dependency_inputs(&CompileOptions::default(), None) + let mut fresh_session = CompilerSession::new(); + fresh_session.update(&changed_source).into_result().unwrap(); + let fresh = fresh_session + .canonical_semantic(&CompileOptions::default()) .unwrap(); - - let invalid = snapshot( - &[( - 51, - "/p/main.rue", - "main.rue", - "fn a() -> i32 { false }\nfn b() -> i32 { 2 }\nfn main() -> i32 { a() + b() }", - )], - 51, - ); - session.update(&invalid).into_result().unwrap(); - assert!( - session - .canonical_semantic(&CompileOptions::default()) - .is_err() - ); - - session.update(&edited).into_result().unwrap(); - let recovered_options = CompileOptions { - opt_level: OptLevel::O2, - ..CompileOptions::default() - }; - let recovered = session.canonical_semantic(&recovered_options).unwrap(); - let mut fresh = CompilerSession::new(); - fresh.update(&edited).into_result().unwrap(); - let fresh = fresh.canonical_semantic(&recovered_options).unwrap(); - assert_eq!( - format!("{:?}", recovered.functions()), - format!("{:?}", fresh.functions()) - ); + assert_body_artifact_parity(&changed, &fresh); + assert_diagnostic_parity(&session, &fresh_session); } #[test] @@ -17205,198 +9152,6 @@ fn main() -> i32 { selected.value() }"#; assert_eq!(root.functions().len(), 1); } - #[test] - fn semantic_fault_injection_copies_the_stale_results_exact_rir_owner() { - let first = SourceSnapshot::single("main.rue", "fn main() -> i32 { 0 }").unwrap(); - let second = SourceSnapshot::single("main.rue", "fn main() -> i32 { 1 }").unwrap(); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.semantic(&options).unwrap(); - session.update(&second).into_result().unwrap(); - session.semantic(&options).unwrap(); - - let records = session - .queries - .semantic - .records() - .cloned() - .collect::>(); - let current = records.last().unwrap(); - let stale = records - .iter() - .rev() - .skip(1) - .find(|record| record.result.is_ok() && record.key != current.key) - .unwrap(); - let stale_rir = stale.rir.as_ref().unwrap().clone(); - let stale_semantic = stale.result.as_ref().unwrap().clone(); - - assert!( - session - .inject_stale_query_for_oracle(crate::unstable::DifferentialOracleFault::Semantic) - ); - let injected = session.queries.semantic.records().last().unwrap(); - assert!(Arc::ptr_eq( - injected.result.as_ref().unwrap(), - &stale_semantic - )); - assert!(Arc::ptr_eq(injected.rir.as_ref().unwrap(), &stale_rir)); - - let view = session.semantic(&options).unwrap(); - let view = Arc::try_unwrap(view).unwrap(); - let (semantic_owner, rir_owner) = view.into_owners(); - assert!(Arc::ptr_eq(&semantic_owner, &stale_semantic)); - assert!(Arc::ptr_eq(&rir_owner, &stale_rir)); - } - - fn body_query_key( - session: &mut CompilerSession, - options: &CompileOptions, - name: &str, - ) -> crate::body_query::BodyQueryKey { - let definitions = session.stable_definitions(options).unwrap(); - let definition = definitions - .definitions() - .iter() - .find(|record| { - record.stable_key().kind() == StableDefinitionKind::Function - && record.stable_key().name() == name - }) - .unwrap() - .stable_key() - .clone(); - crate::body_query::BodyQueryKey { - instance: crate::FunctionInstanceKey::Definition(definition), - configuration: crate::semantic_query_nucleus::SemanticQueryConfiguration { - target: options.target, - preview_features: StablePreviewFeatures::new(&options.preview_features), - }, - } - } - - fn retained_body_query_stamps( - session: &CompilerSession, - key: &crate::body_query::BodyQueryKey, - ) -> (u64, u64, u64, u64) { - let revision = session - .queries - .revisioned - .current_semantic_revision() - .unwrap(); - let cancellation = rue_query::CancellationToken::new(); - let transaction = session - .queries - .revisioned - .body_transaction(revision, key.clone(), cancellation.clone()) - .unwrap(); - let body = session - .queries - .revisioned - .canonical_body_projection(revision, key.clone(), cancellation.clone()) - .unwrap(); - let references = session - .queries - .revisioned - .body_references_projection(revision, key.clone(), cancellation.clone()) - .unwrap(); - let produced_anonymous = session - .queries - .revisioned - .body_produced_anonymous_projection(revision, key.clone(), cancellation) - .unwrap(); - ( - transaction.stamp(), - body.stamp(), - references.stamp(), - produced_anonymous.stamp(), - ) - } - - fn retained_body_transaction( - session: &CompilerSession, - key: &crate::body_query::BodyQueryKey, - ) -> ( - u64, - rue_query::QueryTerminalKind, - crate::body_query::BodyTransaction, - ) { - let revision = session - .queries - .revisioned - .current_semantic_revision() - .unwrap(); - let terminal = session - .queries - .revisioned - .body_transaction(revision, key.clone(), rue_query::CancellationToken::new()) - .unwrap(); - let rue_query::QueryOutcome::Success(transaction) = terminal.outcome() else { - unreachable!("BodyTransaction publishes typed values") - }; - (terminal.stamp(), terminal.kind(), transaction.clone()) - } - - fn retained_body_dependency_nodes( - session: &CompilerSession, - key: &crate::body_query::BodyQueryKey, - ) -> Vec { - let revision = session - .queries - .revisioned - .current_semantic_revision() - .unwrap(); - session - .queries - .revisioned - .body_transaction(revision, key.clone(), rue_query::CancellationToken::new()) - .unwrap() - .dependencies() - .iter() - .map(|dependency| format!("{:?}", dependency.node)) - .collect() - } - - /// A trusted-std `Option` snapshot for the well-known query-edge isolation - /// regression: the root is `root_source`, and the trusted std `Option` module - /// is provided at `\0rue-std/option.rue`, reached with - /// `@import("std/option.rue")` (physical-suffix match). - fn well_known_option_isolation_snapshot(root_source: &str) -> SourceSnapshot { - well_known_option_snapshot_with_source( - root_source, - "pub fn Option(comptime T: type) -> type { enum { Some(T), None } }", - ) - } - - fn well_known_option_snapshot_with_source( - root_source: &str, - option_source: &str, - ) -> SourceSnapshot { - let root = FileId::new(1); - let option = FileId::new(2); - let metadata = SourceMetadata::new_with_trusted_standard_library( - root, - HashMap::from([ - (root, "/project/main.rue".to_owned()), - (option, "/project/std/option.rue".to_owned()), - ]), - HashMap::from([ - (root, "main.rue".to_owned()), - (option, "\0rue-std/option.rue".to_owned()), - ]), - HashSet::from([option]), - ) - .unwrap(); - SourceSnapshot::new( - metadata, - vec![ - (root, Arc::new(root_source.to_owned())), - (option, Arc::new(option_source.to_owned())), - ], - ) - .unwrap() - } - #[test] fn malformed_well_known_option_repairs_to_fresh_canonical_semantics() { let options = CompileOptions::default(); @@ -17570,17 +9325,12 @@ fn main() -> i32 { ); } + #[allow(dead_code)] fn projected_anonymous_nominals( session: &mut CompilerSession, options: &CompileOptions, ) -> Arc<[crate::durable_semantics::DurableAnonymousNominal]> { - session.canonical_rir().unwrap(); - let merged = session - .queries - .rir - .selected_record(&session.queries.graph) - .and_then(|entry| entry.merged.clone()) - .unwrap(); + let merged = session.merge().unwrap(); let revision = session .queries .revisioned @@ -17600,6 +9350,7 @@ fn main() -> i32 { .anonymous_nominals } + #[allow(dead_code)] fn specialized_anonymous_producer( nominal: &crate::durable_semantics::DurableAnonymousNominal, ) -> Option<(&StableDefinitionKey, &crate::CanonicalArguments)> { @@ -17616,6 +9367,7 @@ fn main() -> i32 { Some((definition, arguments)) } + #[allow(dead_code)] fn nested_option_result_facts( facts: &[crate::durable_semantics::DurableAnonymousNominal], ) -> ( @@ -17634,6 +9386,7 @@ fn main() -> i32 { (by_name("Option"), by_name("Result")) } + #[allow(dead_code)] fn assert_nested_result_owns_option_argument( option: &crate::durable_semantics::DurableAnonymousNominal, result: &crate::durable_semantics::DurableAnonymousNominal, @@ -17795,80 +9548,6 @@ fn main() -> i32 { .unwrap(); } - #[test] - fn nested_type_constructor_producers_publish_a_complete_body_closure() { - let first = SourceSnapshot::single( - "main.rue", - r#" - fn Option(comptime T: type) -> type { enum { Some(T), None } } - fn Result(comptime T: type, comptime E: type) -> type { - enum { Ok(T), Err(E) } - } - fn make() -> Result(Option(i32), i32) { - let O = Option(i32); - let R = Result(Option(i32), i32); - R.Ok(O.Some(42)) - } - fn main() -> i32 { - let O = Option(i32); - let R = Result(Option(i32), i32); - match make() { - R.Ok(o) => match o { O.Some(v) => v, O.None => 0 }, - R.Err(e) => 0 - e - } - } - "#, - ) - .unwrap(); - let second = SourceSnapshot::single( - "main.rue", - r#" - fn Option(comptime T: type) -> type { enum { None, Some(T) } } - fn Result(comptime T: type, comptime E: type) -> type { - enum { Ok(T), Err(E) } - } - fn make() -> Result(Option(i32), i32) { - let O = Option(i32); - let R = Result(Option(i32), i32); - R.Ok(O.Some(42)) - } - fn main() -> i32 { - let O = Option(i32); - let R = Result(Option(i32), i32); - match make() { - R.Ok(o) => match o { O.Some(v) => v, O.None => 0 }, - R.Err(e) => 0 - e - } - } - "#, - ) - .unwrap(); - let options = CompileOptions::default(); - let mut session = CompilerSession::new(); - session.update(&first).into_result().unwrap(); - session.canonical_semantic(&options).unwrap(); - let first_projection = projected_anonymous_nominals(&mut session, &options); - let (first_option, first_result) = nested_option_result_facts(&first_projection); - assert_nested_result_owns_option_argument(first_option, first_result, &first_projection); - - session.update(&second).into_result().unwrap(); - let warm = session.canonical_semantic(&options).unwrap(); - let warm_projection = projected_anonymous_nominals(&mut session, &options); - let (warm_option, warm_result) = nested_option_result_facts(&warm_projection); - assert_nested_result_owns_option_argument(warm_option, warm_result, &warm_projection); - assert_ne!( - first_option.shape, warm_option.shape, - "changing only the type constructor's variant order must invalidate its fact" - ); - - let mut fresh = CompilerSession::new(); - fresh.update(&second).into_result().unwrap(); - let expected = fresh.canonical_semantic(&options).unwrap(); - let fresh_projection = projected_anonymous_nominals(&mut fresh, &options); - assert_eq!(warm_projection, fresh_projection); - assert_semantic_artifact_parity(&session, &warm, &expected); - } - #[test] fn anonymous_specialization_dependency_priority_prevents_lexical_starvation() { let source = SourceSnapshot::single( diff --git a/crates/rue-compiler/src/supported_api_inventory.rs b/crates/rue-compiler/src/supported_api_inventory.rs index 4a2cbbfd1..85b8445cf 100644 --- a/crates/rue-compiler/src/supported_api_inventory.rs +++ b/crates/rue-compiler/src/supported_api_inventory.rs @@ -12,9 +12,6 @@ stable|CompilerSession|artifact-query|embedders|import_graph|pub fn import_graph stable|CompilerSession|artifact-query|embedders|published|pub fn published(&self)->Option stable|CompilerSession|artifact-query|embedders|rir|pub fn rir(&mut self)->Result,CompileErrors> stable|CompilerSession|artifact-query|embedders|semantic|pub fn semantic(&mut self,options:&CompileOptions,)->Result,CompileErrors> -stable|CompilerSession|compatibility-boundary|legacy-embedders|close_import_discovery|#[cfg(not(test))]pub fn close_import_discovery(&mut self,ledger:crate::ImportObservationLedger,)->Result,CompileErrors> -stable|CompilerSession|compatibility-boundary|legacy-embedders|import_discovery_plan|pub fn import_discovery_plan(&self,context:crate::ImportDiscoveryContext,)->crate::CompileResult -stable|CompilerSession|compatibility-boundary|legacy-embedders|stage_import_discovery|pub fn stage_import_discovery(&mut self,snapshot:&SourceSnapshot,context:crate::ImportDiscoveryContext,accepted_reads:Arc<[crate::AcceptedReadManifestEntry]>,carried_ledger:crate::ImportObservationLedger,)->Result stable|CompilerSession|diagnostic-query|embedders|last_good_semantic_diagnostics|pub fn last_good_semantic_diagnostics(&self)->Option<&Arc> stable|CompilerSession|diagnostic-query|embedders|latest_diagnostics|pub fn latest_diagnostics(&self)->Option<&Arc> stable|CompilerSession|diagnostic-query|embedders|latest_successful_diagnostics|pub fn latest_successful_diagnostics(&self)->Option<&Arc> @@ -101,8 +98,6 @@ stable|toolchain_module_demand|toolchain-module-demand|source-loaders+embedders| stable|toolchain_module_demand|toolchain-module-demand|source-loaders+embedders|ParkedToolchainModules|pub use toolchain_module_demand::ParkedToolchainModules stable|toolchain_module_demand|toolchain-module-demand|source-loaders+embedders|STRBUF_MODULE_LOGICAL_PATH|pub use toolchain_module_demand::STRBUF_MODULE_LOGICAL_PATH stable|toolchain_module_demand|toolchain-module-demand|source-loaders+embedders|TrustedToolchainModuleDemand|pub use toolchain_module_demand::TrustedToolchainModuleDemand -unstable|CompilerSession|debug-tooling|in-tree-tooling|unstable_dependency_baseline|pub fn unstable_dependency_baseline(&mut self,options:&CompileOptions,std_dir:Option<&str>,)->Result,CompileErrors> -unstable|CompilerSession|debug-tooling|in-tree-tooling|unstable_invalidation_metrics|pub fn unstable_invalidation_metrics(&mut self,previous:&crate::unstable::DependencyBaseline,current:&crate::unstable::DependencyBaseline,)->Result unstable|CompilerSession|debug-tooling|in-tree-tooling|unstable_metrics|pub fn unstable_metrics(&self)->crate::unstable::MetricsSnapshot unstable|CompilerSession|debug-tooling|in-tree-tooling|unstable_present|pub fn unstable_present(&mut self,request:PresentationRequest<'_>,)->Result unstable|CompilerSessionUpdate|debug-tooling|in-tree-tooling|unstable_metrics|pub fn unstable_metrics(&self)->crate::unstable::ParseMetrics diff --git a/crates/rue-compiler/src/test_support.rs b/crates/rue-compiler/src/test_support.rs index 2a3db637d..6d0e67058 100644 --- a/crates/rue-compiler/src/test_support.rs +++ b/crates/rue-compiler/src/test_support.rs @@ -30,12 +30,12 @@ pub(crate) fn test_air(source: &str) -> MultiErrorResult { pub(crate) fn test_cfg(source: &str) -> MultiErrorResult { let snapshot = SourceSnapshot::single("", source).map_err(CompileErrors::from)?; let (rir, semantic, _) = test_frontend_snapshot(&snapshot, &CompileOptions::default())?; - let rir = - std::sync::Arc::try_unwrap(rir).expect("test session uniquely owns its RIR after return"); let semantic = std::sync::Arc::try_unwrap(semantic) .expect("test session uniquely owns its semantic output after return"); - let (_, symbols) = rir.into_parts(); let (functions, type_pool, strings, warnings) = semantic.into_parts(); + let rir = + std::sync::Arc::try_unwrap(rir).expect("test session uniquely owns its RIR after return"); + let (_, symbols) = rir.into_parts(); Ok(CompileState { interner: { use lasso::Key; @@ -125,8 +125,9 @@ pub(crate) fn test_frontend_snapshot( )> { let mut session = CompilerSession::new(); publish_test_snapshot(&mut session, snapshot)?; - let rir = session.canonical_rir()?; + let _rir = session.canonical_rir()?; let semantic = session.canonical_semantic(options)?; + let rir = semantic.rir_owner().clone(); let work = session.work().clone(); drop(session); Ok((rir, semantic, work)) diff --git a/crates/rue-compiler/src/typed_query_store.rs b/crates/rue-compiler/src/typed_query_store.rs index 20d55068e..48560459d 100644 --- a/crates/rue-compiler/src/typed_query_store.rs +++ b/crates/rue-compiler/src/typed_query_store.rs @@ -1,30 +1,13 @@ -//! Shared typed storage for immutable query terminal attempts. +//! Compatibility traits for revisioned query-family records and lifecycle views. //! -//! The store owns lookup, insertion validation, and deterministic retention. -//! `CompilerSession` remains responsible for deciding when and how a query is -//! computed; this component never calls compiler phases. +//! Storage and dependency authority live in `RevisionedQueryDatabase`. This +//! module deliberately contains no selected-state cache or peer graph. -use std::collections::{BTreeSet, VecDeque}; -use std::marker::PhantomData; use std::sync::Arc; -use crate::query_graph::{ - DependencyStamp, InvalidationCause, ObservedDependency, QueryGraph, TypedNode, -}; use crate::session::{AttemptId, QueryStructuralWork}; -/// Deterministic FIFO retention for typed query families. -/// -/// Records survive source publication changes. Their declared dependency -/// edges decide whether they remain reusable; the bound prevents old source -/// and option variants from creating unbounded session-owned history. -/// -/// `LIMIT + 1` must not exceed the number of distinct -/// `(preview_features, target)` query keys the eviction tests can enumerate with -/// two preview features and three targets (`2^2 * 3 = 12`). Stabilizing a -/// preview feature shrinks that key space, so the retention bound tracks it -/// (RUE-987 took it from `16` to `10` when `aggregate_layout` retired); see -/// `session::tests::retention_variants`. +/// Bounded revisioned-family retention used by the canonical runtime. pub(crate) const QUERY_TERMINAL_RETENTION_LIMIT: usize = 10; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,34 +16,15 @@ pub(crate) enum TerminalKind { Failure, } -/// Session-local publication identity for either kind of terminal outcome. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum OutcomePublication { - Success(u64), - Failure(u64), -} - -/// Identity observed by dependents. Attempt/work identity is deliberately -/// absent: only the typed outcome and its immutable diagnostic batch matter. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct TerminalStamp { - pub(crate) outcome: OutcomePublication, - pub(crate) diagnostics: u64, -} - pub(crate) trait TypedQueryFamily: std::fmt::Debug + Send + Sync { type Key: Eq + std::hash::Hash + Clone + std::fmt::Debug + Send + Sync; type Record: std::fmt::Debug + Clone + Send + Sync; const MAX_TERMINALS: usize; - const MAX_TOMBSTONES: usize = Self::MAX_TERMINALS; + #[allow(dead_code)] fn key(record: &Self::Record) -> &Self::Key; fn terminal_kind(record: &Self::Record) -> TerminalKind; - /// Exhaustive family-owned equality for the success value or failure - /// payload. Called only for records with the same terminal variant. fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool; - /// Exhaustive family-owned equality for the attached diagnostic batch, - /// including the empty batch. fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool; fn diagnostics(record: &Self::Record) -> Option<&Arc> { let _ = record; @@ -69,20 +33,6 @@ pub(crate) trait TypedQueryFamily: std::fmt::Debug + Send + Sync { fn record_is_consistent(record: &Self::Record) -> bool; } -/// A family-owned secondary index predicate with a statically complete key. -/// -/// This supports deliberate cross-variant reuse without exposing records or an -/// arbitrary predicate to `CompilerSession`. -pub(crate) trait TypedSecondaryLookupFamily: TypedQueryFamily { - type SecondaryKey: Eq; - - fn matches_secondary(record: &Self::Record, key: &Self::SecondaryKey) -> bool; -} - -pub(crate) trait TypedEquivalentLookupFamily: TypedSecondaryLookupFamily { - fn rekey_equivalent(record: &Self::Record, key: Self::Key) -> Option; -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum AttemptExecution { Computed, @@ -91,6 +41,12 @@ pub(crate) enum AttemptExecution { Rejected, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum AbortedQueryReason { + Canceled, + DependencyCycle, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum AttemptOutcomeKind { Success, @@ -98,187 +54,14 @@ pub(crate) enum AttemptOutcomeKind { Aborted(AbortedQueryReason), } -#[derive(Debug)] -enum AttemptPayload { - Terminal { - stamp: TerminalStamp, - node: TypedNode, - record: F::Record, - }, - Reused { - origin: Arc>, - }, - Aborted { - reason: AbortedQueryReason, - diagnostics: Option>, - }, -} - -pub(crate) trait AttemptView: std::fmt::Debug + Send + Sync { - fn id(&self) -> AttemptId; - fn execution(&self) -> AttemptExecution; - fn outcome(&self) -> AttemptOutcomeKind; - fn origin_id(&self) -> AttemptId; - fn dependencies(&self) -> &[ObservedDependency]; - /// Runtime-native observations which cannot be forged into legacy graph - /// node identities during family migration. - fn runtime_observations(&self) -> &[RuntimeObservation] { - &[] - } - /// Runtime-owned reduced work for revisioned-runtime attempts. This is - /// separate from family-typed structural work during migration. - fn runtime_work(&self) -> &[(Arc, u64)] { - &[] - } - fn work(&self) -> &QueryStructuralWork; - fn diagnostics(&self) -> Option<&Arc>; -} - -/// Explicit compatibility projection for revisioned-runtime provenance. -/// -/// These values remain runtime-owned; adapters must not allocate peer -/// `QueryGraph` nodes merely to satisfy the legacy `dependencies()` shape. +/// Runtime-native observations are retained without allocating compatibility +/// graph nodes. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum RuntimeObservation { Dependency(rue_query::Observation), Input(rue_query::InputObservation), } -/// The sole immutable record of one request to a typed query family. -/// -/// Computed attempts own their terminal value, diagnostic batch (nested in the -/// family record), dependency edges, and structural work. Reuse attempts own -/// no peer copies: they point at the computed terminal origin and carry zero -/// work. Aborts have no terminal stamp or reusable value, but retain the exact -/// dependency prefix, diagnostic batch (when one was frozen), and work prefix. -#[derive(Debug)] -pub(crate) struct Attempt { - pub(crate) id: AttemptId, - pub(crate) key: F::Key, - pub(crate) execution: AttemptExecution, - payload: AttemptPayload, - pub(crate) dependencies: Arc<[ObservedDependency]>, - pub(crate) work: QueryStructuralWork, -} - -impl Attempt { - fn terminal_origin(&self) -> Option<&Attempt> { - match &self.payload { - AttemptPayload::Terminal { .. } => Some(self), - AttemptPayload::Reused { origin } => origin.terminal_origin(), - AttemptPayload::Aborted { .. } => None, - } - } - - fn record(&self) -> Option<&F::Record> { - match &self.terminal_origin()?.payload { - AttemptPayload::Terminal { record, stamp, .. } => { - debug_assert_eq!( - matches!(stamp.outcome, OutcomePublication::Success(_)), - matches!(F::terminal_kind(record), TerminalKind::Success), - "terminal stamp kind must match its immutable record" - ); - Some(record) - } - AttemptPayload::Reused { .. } | AttemptPayload::Aborted { .. } => unreachable!(), - } - } - - fn stamp(&self) -> Option { - match &self.terminal_origin()?.payload { - AttemptPayload::Terminal { stamp, .. } => Some(*stamp), - AttemptPayload::Reused { .. } | AttemptPayload::Aborted { .. } => unreachable!(), - } - } - - fn node(&self) -> Option> { - match &self.terminal_origin()?.payload { - AttemptPayload::Terminal { node, .. } => Some(node.clone()), - AttemptPayload::Reused { .. } | AttemptPayload::Aborted { .. } => unreachable!(), - } - } - - pub(crate) fn origin_id(&self) -> AttemptId { - self.terminal_origin().map_or(self.id, |origin| origin.id) - } - - #[cfg(test)] - pub(crate) fn aborted_reason(&self) -> Option { - match self.payload { - AttemptPayload::Aborted { reason, .. } => Some(reason), - _ => None, - } - } -} - -impl AttemptView for Attempt { - fn id(&self) -> AttemptId { - self.id - } - - fn execution(&self) -> AttemptExecution { - self.execution - } - - fn outcome(&self) -> AttemptOutcomeKind { - match &self.payload { - AttemptPayload::Terminal { record, .. } => match F::terminal_kind(record) { - TerminalKind::Success => AttemptOutcomeKind::Success, - TerminalKind::Failure => AttemptOutcomeKind::Failure, - }, - AttemptPayload::Reused { origin } => origin.outcome(), - AttemptPayload::Aborted { reason, .. } => AttemptOutcomeKind::Aborted(*reason), - } - } - - fn origin_id(&self) -> AttemptId { - Attempt::origin_id(self) - } - - fn dependencies(&self) -> &[ObservedDependency] { - &self.dependencies - } - - fn work(&self) -> &QueryStructuralWork { - &self.work - } - - fn diagnostics(&self) -> Option<&Arc> { - match &self.payload { - AttemptPayload::Aborted { diagnostics, .. } => diagnostics.as_ref(), - AttemptPayload::Terminal { .. } | AttemptPayload::Reused { .. } => { - self.record().and_then(F::diagnostics) - } - } - } -} - -#[derive(Debug)] -struct ValidationTombstone { - key: F::Key, - stamp: TerminalStamp, - node: TypedNode, - dependencies: Vec, -} - -impl Clone for ValidationTombstone { - fn clone(&self) -> Self { - Self { - key: self.key.clone(), - stamp: self.stamp, - node: self.node, - dependencies: self.dependencies.clone(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum AbortedQueryReason { - Canceled, - DuplicateInFlight, - DependencyCycle, -} - #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub(crate) struct QueryStoreRetention { pub(crate) retained: usize, @@ -288,1363 +71,18 @@ pub(crate) struct QueryStoreRetention { pub(crate) evictions: usize, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum BeginSelectedError { - DuplicateInFlight, - DependencyCycle, - KeyNotSelected, - AlreadyTerminal, -} - -#[derive(Debug)] -pub(crate) enum SelectedQueryState { - Unpublished { - key: F::Key, - }, - Dirty { - key: F::Key, - retained: Option>, - invalidated_by: BTreeSet, - }, - Computing { - key: F::Key, - attempt_id: u64, - last_good: Option>, - }, - Success { - key: F::Key, - current: TerminalHandle, - last_good: TerminalHandle, - }, - Failure { - key: F::Key, - current: TerminalHandle, - last_good: Option>, - }, -} - -impl Clone for SelectedQueryState { - fn clone(&self) -> Self { - match self { - Self::Unpublished { key } => Self::Unpublished { key: key.clone() }, - Self::Dirty { - key, - retained, - invalidated_by, - } => Self::Dirty { - key: key.clone(), - retained: retained.clone(), - invalidated_by: invalidated_by.clone(), - }, - Self::Computing { - key, - attempt_id, - last_good, - } => Self::Computing { - key: key.clone(), - attempt_id: *attempt_id, - last_good: last_good.clone(), - }, - Self::Success { - key, - current, - last_good, - } => Self::Success { - key: key.clone(), - current: current.clone(), - last_good: last_good.clone(), - }, - Self::Failure { - key, - current, - last_good, - } => Self::Failure { - key: key.clone(), - current: current.clone(), - last_good: last_good.clone(), - }, - } - } -} - -impl SelectedQueryState { - pub(crate) fn key(&self) -> &F::Key { - match self { - Self::Unpublished { key } - | Self::Dirty { key, .. } - | Self::Computing { key, .. } - | Self::Success { key, .. } - | Self::Failure { key, .. } => key, - } - } -} - -/// Bounded memo table for one statically typed query family. -#[derive(Debug)] -pub(crate) struct TypedQueryStore { - terminals: VecDeque>>, - history: VecDeque>>, - tombstones: VecDeque>, - next_publication: u64, - evictions: usize, - selected: Option>, - last_good: Option>, - family: PhantomData F>, -} - -#[derive(Debug)] -pub(crate) struct TerminalHandle { - attempt: Arc>, -} - -impl Clone for TerminalHandle { - fn clone(&self) -> Self { - Self { - attempt: self.attempt.clone(), - } - } -} - -impl TerminalHandle { - fn node_id(&self) -> crate::query_graph::QueryNodeId { - self.attempt.node().unwrap().id() - } - - pub(crate) fn observed(&self) -> ObservedDependency { - ObservedDependency { - node: self.attempt.node().unwrap().id(), - stamp: DependencyStamp::Terminal(self.attempt.stamp().unwrap()), - } - } - - pub(crate) fn is_valid(&self, graph: &mut QueryGraph) -> bool { - graph.valid::(self.attempt.node().unwrap().id()) - } - - #[cfg(test)] - pub(crate) fn invalidation_cause_count(&self, graph: &QueryGraph) -> usize { - graph - .invalidation_causes::(self.attempt.node().unwrap().id()) - .len() - } -} - -impl TerminalHandle { - #[cfg(test)] - pub(crate) fn stamp(&self) -> TerminalStamp { - self.attempt.stamp().unwrap() - } - - pub(crate) fn origin_attempt_id(&self) -> AttemptId { - self.attempt.origin_id() - } - - pub(crate) fn as_view(&self) -> Arc - where - F: 'static, - { - self.attempt.clone() - } - - #[cfg(test)] - pub(crate) fn attempt(&self) -> &Arc> { - &self.attempt - } -} - -impl Clone for TypedQueryStore { - fn clone(&self) -> Self { - Self { - terminals: self.terminals.clone(), - history: self.history.clone(), - tombstones: self.tombstones.clone(), - next_publication: self.next_publication, - evictions: self.evictions, - selected: self.selected.clone(), - last_good: self.last_good.clone(), - family: PhantomData, - } - } -} - -impl Default for TypedQueryStore { - fn default() -> Self { - Self { - terminals: VecDeque::new(), - history: VecDeque::new(), - tombstones: VecDeque::new(), - next_publication: 0, - evictions: 0, - selected: None, - last_good: None, - family: PhantomData, - } - } -} - -impl TypedQueryStore { - /// Select and validate one exact key, returning its immutable terminal or - /// atomically entering Computing. Every session query uses this exhaustive - /// boundary instead of independently scanning memo records. - pub(crate) fn request_selected( - &mut self, - graph: &mut QueryGraph, - key: F::Key, - attempt_id: u64, - ) -> Result)>, BeginSelectedError> - where - F: 'static, - { - self.recover_aborted_cycle(graph); - if let Some(SelectedQueryState::Computing { - key: active, - attempt_id: active_attempt, - .. - }) = self.selected.as_ref() - && active != &key - { - let _ = active_attempt; - self.push_abort( - attempt_id, - key, - AbortedQueryReason::DuplicateInFlight, - QueryStructuralWork::None, - AttemptExecution::Rejected, - [], - None, - ); - return Err(BeginSelectedError::DuplicateInFlight); - } - self.select(graph, key.clone()); - match self.selected.as_ref() { - Some(SelectedQueryState::Success { current, .. }) - | Some(SelectedQueryState::Failure { current, .. }) => { - let origin = current.attempt.clone(); - let record = self - .record_for_handle(current) - .expect("selected terminal retains its artifact") - .clone(); - if F::terminal_kind(&record) == TerminalKind::Success { - self.last_good = Some(current.clone()); - } - let request = Arc::new(Attempt { - id: AttemptId(attempt_id), - key, - execution: AttemptExecution::Reused, - payload: AttemptPayload::Reused { origin }, - dependencies: Arc::from([]), - work: QueryStructuralWork::None, - }); - self.push_history(request.clone()); - Ok(Some((record, TerminalHandle { attempt: request }))) - } - Some(SelectedQueryState::Unpublished { .. } | SelectedQueryState::Dirty { .. }) => { - self.begin_selected(graph, &key, attempt_id)?; - Ok(None) - } - Some(SelectedQueryState::Computing { .. }) => { - self.begin_selected(graph, &key, attempt_id)?; - unreachable!("a computing request is rejected by begin_selected") - } - None => Err(BeginSelectedError::KeyNotSelected), - } - } - - pub(crate) fn select(&mut self, graph: &mut QueryGraph, key: F::Key) - where - F: 'static, - { - if self - .selected - .as_ref() - .is_some_and(|selected| selected.key() == &key) - && matches!(self.selected, Some(SelectedQueryState::Computing { .. })) - { - return; - } - let retained = self.handle(&key); - self.selected = match retained { - Some(handle) if handle.is_valid(graph) => match self - .get(&key) - .map(F::terminal_kind) - .expect("retained handle belongs to a retained record") - { - TerminalKind::Success => Some(SelectedQueryState::Success { - key, - current: handle.clone(), - last_good: self.last_good.clone().unwrap_or(handle), - }), - TerminalKind::Failure => Some(SelectedQueryState::Failure { - key, - current: handle, - last_good: self.last_good.clone(), - }), - }, - retained @ Some(_) => { - let invalidated_by = retained - .as_ref() - .map(|handle| graph.invalidation_causes::(handle.node_id()).clone()) - .unwrap_or_default(); - Some(SelectedQueryState::Dirty { - key, - retained, - invalidated_by, - }) - } - None => Some(SelectedQueryState::Unpublished { key }), - }; - } - - #[cfg(test)] - fn synchronize_selected(&mut self, graph: &QueryGraph) - where - F: 'static, - { - let invalidated = match self.selected.as_ref() { - Some( - SelectedQueryState::Success { key, current, .. } - | SelectedQueryState::Failure { key, current, .. }, - ) if !graph.is_current::(current.node_id()) => Some((key.clone(), current.clone())), - _ => None, - }; - if let Some((key, retained)) = invalidated { - let invalidated_by = graph.invalidation_causes::(retained.node_id()).clone(); - self.selected = Some(SelectedQueryState::Dirty { - key, - retained: Some(retained), - invalidated_by, - }); - } - } - - #[cfg(test)] - pub(crate) fn selected_state(&mut self, graph: &QueryGraph) -> Option<&SelectedQueryState> - where - F: 'static, - { - self.synchronize_selected(graph); - self.selected.as_ref() - } - - pub(crate) fn computing_key(&self) -> Option { - match self.selected.as_ref()? { - SelectedQueryState::Computing { key, .. } => Some(key.clone()), - _ => None, - } - } - - pub(crate) fn selected_record(&self, graph: &QueryGraph) -> Option<&F::Record> - where - F: 'static, - { - let handle = match self.selected.as_ref()? { - SelectedQueryState::Success { current, .. } - | SelectedQueryState::Failure { current, .. } => current, - _ => return None, - }; - if !graph.is_current::(handle.node_id()) { - return None; - } - self.record_for_handle(handle) - } - - pub(crate) fn selected_handle(&self, graph: &QueryGraph) -> Option> - where - F: 'static, - { - let handle = match self.selected.as_ref()? { - SelectedQueryState::Success { current, .. } - | SelectedQueryState::Failure { current, .. } => current, - _ => return None, - }; - graph - .is_current::(handle.node_id()) - .then(|| handle.clone()) - } - - pub(crate) fn last_good_record(&self) -> Option<&F::Record> { - self.last_good - .as_ref() - .and_then(|handle| self.record_for_handle(handle)) - } - - pub(crate) fn begin_selected( - &mut self, - graph: &mut QueryGraph, - key: &F::Key, - attempt_id: u64, - ) -> Result<(), BeginSelectedError> - where - F: 'static, - { - self.recover_aborted_cycle(graph); - let selected = self - .selected - .as_ref() - .ok_or(BeginSelectedError::KeyNotSelected)?; - if selected.key() != key { - return Err(BeginSelectedError::KeyNotSelected); - } - match selected { - SelectedQueryState::Computing { .. } => { - let reason = if graph.is_current_computation::() { - AbortedQueryReason::DuplicateInFlight - } else { - AbortedQueryReason::DependencyCycle - }; - if reason == AbortedQueryReason::DependencyCycle { - graph.abort_cycle::(); - self.recover_aborted_cycle(graph); - } - self.push_abort( - attempt_id, - key.clone(), - reason, - QueryStructuralWork::None, - AttemptExecution::Rejected, - [], - None, - ); - return Err(match reason { - AbortedQueryReason::DependencyCycle => BeginSelectedError::DependencyCycle, - AbortedQueryReason::DuplicateInFlight => BeginSelectedError::DuplicateInFlight, - AbortedQueryReason::Canceled => unreachable!(), - }); - } - SelectedQueryState::Success { .. } | SelectedQueryState::Failure { .. } => { - return Err(BeginSelectedError::AlreadyTerminal); - } - SelectedQueryState::Unpublished { .. } | SelectedQueryState::Dirty { .. } => {} - } - self.selected = Some(SelectedQueryState::Computing { - key: key.clone(), - attempt_id, - last_good: self.last_good.clone(), - }); - graph.begin_computation::(); - Ok(()) - } - - /// Project a graph-level cycle abort back into this family's authoritative - /// selected state. The graph marks every guard in the cycle; each typed - /// store consumes its mark at its next boundary, without publishing a - /// terminal or requiring callers to cancel partially unwound work. - pub(crate) fn recover_aborted_cycle(&mut self, graph: &mut QueryGraph) -> bool - where - F: 'static, - { - if !graph.take_aborted::() { - return false; - } - let Some(SelectedQueryState::Computing { - key, attempt_id, .. - }) = self.selected.take() - else { - return true; - }; - let retained = self.handle(&key); - let invalidated_by = retained - .as_ref() - .map(|handle| graph.invalidation_causes::(handle.node_id()).clone()) - .unwrap_or_default(); - self.push_abort( - attempt_id, - key.clone(), - AbortedQueryReason::DependencyCycle, - QueryStructuralWork::None, - AttemptExecution::Rejected, - [], - None, - ); - self.selected = Some(SelectedQueryState::Dirty { - key, - retained, - invalidated_by, - }); - true - } - - pub(crate) fn publish_selected( - &mut self, - graph: &mut QueryGraph, - record: F::Record, - dependencies: impl IntoIterator, - ) -> Result, BeginSelectedError> - where - F: 'static, - { - self.publish_selected_with_work(graph, record, dependencies, QueryStructuralWork::None) - } - - pub(crate) fn publish_selected_with_work( - &mut self, - graph: &mut QueryGraph, - record: F::Record, - dependencies: impl IntoIterator, - work: QueryStructuralWork, - ) -> Result, BeginSelectedError> - where - F: 'static, - { - self.publish_selected_attempt( - graph, - record, - dependencies, - work, - AttemptExecution::Computed, - ) - } - - pub(crate) fn publish_selected_attempt( - &mut self, - graph: &mut QueryGraph, - record: F::Record, - dependencies: impl IntoIterator, - work: QueryStructuralWork, - execution: AttemptExecution, - ) -> Result, BeginSelectedError> - where - F: 'static, - { - if self.recover_aborted_cycle(graph) { - return Err(BeginSelectedError::DependencyCycle); - } - let key = F::key(&record).clone(); - let attempt_id = match self.selected.as_ref() { - Some(SelectedQueryState::Computing { - key: selected, - attempt_id, - .. - }) if selected == &key => *attempt_id, - _ => panic!("terminal publication requires its selected computation"), - }; - let kind = F::terminal_kind(&record); - let handle = self.insert_with_dependencies_and_work( - graph, - AttemptId(attempt_id), - record, - dependencies, - work, - execution, - ); - graph.finish_computation::(); - self.selected = Some(match kind { - TerminalKind::Success => { - self.last_good = Some(handle.clone()); - SelectedQueryState::Success { - key, - current: handle.clone(), - last_good: handle.clone(), - } - } - TerminalKind::Failure => SelectedQueryState::Failure { - key, - current: handle.clone(), - last_good: self.last_good.clone(), - }, - }); - Ok(handle) - } - - #[cfg(test)] - pub(crate) fn cancel_selected(&mut self, graph: &mut QueryGraph, attempt_id: u64) - where - F: 'static, - { - let Some(SelectedQueryState::Computing { key, .. }) = self.selected.take() else { - return; - }; - let retained = self.handle(&key); - if let Some(handle) = &retained { - graph.disappear::(handle.node_id()); - } - graph.finish_computation::(); - self.push_abort( - attempt_id, - key.clone(), - AbortedQueryReason::Canceled, - QueryStructuralWork::None, - AttemptExecution::Computed, - [], - None, - ); - self.selected = Some(SelectedQueryState::Dirty { - key, - retained, - invalidated_by: BTreeSet::new(), - }); - } - - fn push_abort( - &mut self, - attempt_id: u64, - key: F::Key, - reason: AbortedQueryReason, - work: QueryStructuralWork, - execution: AttemptExecution, - dependencies: impl IntoIterator, - diagnostics: Option>, - ) -> Arc> { - let attempt = Arc::new(Attempt { - id: AttemptId(attempt_id), - key, - execution, - payload: AttemptPayload::Aborted { - reason, - diagnostics, - }, - dependencies: dependencies.into_iter().collect(), - work, - }); - self.push_history(attempt.clone()); - attempt - } - - fn push_history(&mut self, attempt: Arc>) { - let _ = ( - &attempt.key, - attempt.execution, - &attempt.dependencies, - &attempt.work, - ); - let _ = AttemptExecution::Adopted; - self.history.push_back(attempt); - while self.history.len() > F::MAX_TERMINALS { - self.history.pop_front(); - } - } - - /// Freeze an aborted request without rolling back completed dependencies. - /// Terminal publication is the only operation that mutates this family's - /// graph edges, so abandoning a provisional computation only has to close - /// its graph guard and restore the retained terminal as Dirty. - pub(crate) fn record_aborted_attempt( - &mut self, - graph: &mut QueryGraph, - key: F::Key, - attempt_id: u64, - reason: AbortedQueryReason, - work: QueryStructuralWork, - dependencies: impl IntoIterator, - diagnostics: Option>, - ) -> Arc - where - F: 'static, - { - if matches!( - self.selected.as_ref(), - Some(SelectedQueryState::Computing { key: selected, .. }) if selected == &key - ) { - graph.finish_computation::(); - } - let retained = self.handle(&key); - let invalidated_by = retained - .as_ref() - .map(|handle| graph.invalidation_causes::(handle.node_id()).clone()) - .unwrap_or_default(); - let attempt = self.push_abort( - attempt_id, - key.clone(), - reason, - work, - AttemptExecution::Computed, - dependencies, - diagnostics, - ); - self.selected = Some(SelectedQueryState::Dirty { - key, - retained, - invalidated_by, - }); - attempt - } - - fn record_for_handle<'a>(&self, handle: &'a TerminalHandle) -> Option<&'a F::Record> { - handle.attempt.record() - } - - pub(crate) fn get(&self, key: &F::Key) -> Option<&F::Record> { - self.terminals - .iter() - .find_map(|attempt| attempt.record().filter(|record| F::key(record) == key)) - } - - pub(crate) fn get_with_handle(&self, key: &F::Key) -> Option<(&F::Record, TerminalHandle)> { - self.terminals - .iter() - .find(|attempt| attempt.record().is_some_and(|record| F::key(record) == key)) - .map(|attempt| { - ( - attempt.record().unwrap(), - TerminalHandle { - attempt: attempt.clone(), - }, - ) - }) - } - - pub(crate) fn insert_with_dependencies( - &mut self, - graph: &mut QueryGraph, - record: F::Record, - dependencies: impl IntoIterator, - ) -> TerminalHandle - where - F: 'static, - { - self.insert_with_dependencies_and_work( - graph, - AttemptId(0), - record, - dependencies, - QueryStructuralWork::None, - AttemptExecution::Computed, - ) - } - - pub(crate) fn insert_with_dependencies_and_work( - &mut self, - graph: &mut QueryGraph, - attempt_id: AttemptId, - record: F::Record, - dependencies: impl IntoIterator, - work: QueryStructuralWork, - execution: AttemptExecution, - ) -> TerminalHandle - where - F: 'static, - { - assert!( - F::record_is_consistent(&record), - "typed query record key does not match its terminal artifact revision" - ); - // A complete intrinsic key can legitimately be selected again after a - // direct dependency stamp changes (for example request-local FileIds - // after relocation). Supersede that key's dirty terminal atomically; - // ordinary valid requests never reach insertion because they reuse it. - let dependencies = dependencies.into_iter().collect::>(); - if let Some(index) = self - .tombstones - .iter() - .position(|tombstone| &tombstone.key == F::key(&record)) - { - // The artifact needed for typed equality was evicted. Its next - // publication must be fresh even when the recomputed value happens - // to be equal, and retained dependents must observe disappearance. - let tombstone = self.tombstones.remove(index).unwrap(); - graph.retire::(tombstone.node.id()); - } - let previous = self - .terminals - .iter() - .position(|attempt| { - attempt - .record() - .is_some_and(|previous| F::key(previous) == F::key(&record)) - }) - .map(|index| self.terminals.remove(index).unwrap()); - let mut allocate = || { - let publication = self.next_publication; - self.next_publication = self.next_publication.wrapping_add(1); - publication - }; - let outcome = previous - .as_ref() - .filter(|attempt| { - F::terminal_kind(attempt.record().unwrap()) == F::terminal_kind(&record) - && F::outcome_equal(attempt.record().unwrap(), &record) - }) - .map_or_else( - || match F::terminal_kind(&record) { - TerminalKind::Success => OutcomePublication::Success(allocate()), - TerminalKind::Failure => OutcomePublication::Failure(allocate()), - }, - |attempt| attempt.stamp().unwrap().outcome, - ); - let diagnostics = previous - .as_ref() - .filter(|attempt| F::diagnostics_equal(attempt.record().unwrap(), &record)) - .map_or_else(&mut allocate, |attempt| { - attempt.stamp().unwrap().diagnostics - }); - let stamp = TerminalStamp { - outcome, - diagnostics, - }; - let node = previous - .as_ref() - .and_then(|attempt| attempt.node()) - .unwrap_or_else(|| TypedNode::::allocate(graph)); - graph.publish::( - node.id(), - DependencyStamp::Terminal(stamp), - dependencies.iter().copied(), - ); - let attempt = Arc::new(Attempt { - id: attempt_id, - key: F::key(&record).clone(), - execution, - payload: AttemptPayload::Terminal { - stamp, - node: node.clone(), - record, - }, - dependencies: dependencies.clone().into(), - work, - }); - self.terminals.push_back(attempt.clone()); - self.push_history(attempt.clone()); - while self.terminals.len() > F::MAX_TERMINALS { - let protected = |attempt: &Arc>| { - let node = attempt.node().unwrap().id(); - self.last_good - .as_ref() - .is_some_and(|handle| handle.node_id() == node) - || self - .selected - .as_ref() - .is_some_and(|selected| match selected { - SelectedQueryState::Success { current, .. } - | SelectedQueryState::Failure { current, .. } => { - current.node_id() == node - } - SelectedQueryState::Dirty { - retained: Some(retained), - .. - } => retained.node_id() == node, - _ => false, - }) - }; - let index = self - .terminals - .iter() - .position(|attempt| !protected(attempt)) - .expect("terminal bound exceeds the constant-size protected selected set"); - let evicted = self.terminals.remove(index).unwrap(); - self.tombstones.push_back(ValidationTombstone { - key: F::key(evicted.record().unwrap()).clone(), - stamp: evicted.stamp().unwrap(), - node: evicted.node().unwrap(), - dependencies: graph.direct_dependencies::(evicted.node().unwrap().id()), - }); - self.evictions += 1; - } - while self.tombstones.len() > F::MAX_TOMBSTONES { - let tombstone = self - .tombstones - .pop_front() - .expect("retention overflow always has an evicted tombstone"); - graph.retire::(tombstone.node.id()); - } - TerminalHandle { attempt } - } - - #[cfg(test)] - pub(crate) fn insert(&mut self, record: F::Record) -> TerminalStamp - where - F: 'static, - { - let mut graph = QueryGraph::default(); - self.insert_with_dependencies(&mut graph, record, []) - .stamp() - } - - pub(crate) fn len(&self) -> usize { - self.terminals.len() - } - - pub(crate) fn records(&self) -> impl Iterator { - self.terminals.iter().filter_map(|attempt| attempt.record()) - } - - pub(crate) fn handle(&self, key: &F::Key) -> Option> { - self.terminals - .iter() - .find(|attempt| attempt.record().is_some_and(|record| F::key(record) == key)) - .map(|attempt| TerminalHandle { - attempt: attempt.clone(), - }) - } - - pub(crate) fn evictions(&self) -> usize { - self.evictions - } - - fn protected_count(&self) -> usize - where - F: 'static, - { - let mut protected = Vec::new(); - if let Some(last_good) = &self.last_good { - protected.push(last_good.node_id()); - } - if let Some(selected) = &self.selected { - let current = match selected { - SelectedQueryState::Success { current, .. } - | SelectedQueryState::Failure { current, .. } => Some(current.node_id()), - SelectedQueryState::Dirty { - retained: Some(retained), - .. - } => Some(retained.node_id()), - _ => None, - }; - if let Some(current) = current - && !protected.contains(¤t) - { - protected.push(current); - } - } - protected.len() - } - - pub(crate) fn retention(&self, graph: &QueryGraph) -> QueryStoreRetention - where - F: 'static, - { - QueryStoreRetention { - retained: self.terminals.len(), - protected: self.protected_count(), - pinned: self - .terminals - .iter() - .filter(|attempt| { - graph.reverse_dependency_count::(attempt.node().unwrap().id()) != 0 - }) - .count() - + self - .tombstones - .iter() - .filter(|tombstone| { - graph.reverse_dependency_count::(tombstone.node.id()) != 0 - }) - .count(), - tombstones: self.tombstones.len(), - evictions: self.evictions, - } - } - - pub(crate) fn aborted_len(&self) -> usize { - self.history - .iter() - .filter(|attempt| matches!(attempt.payload, AttemptPayload::Aborted { .. })) - .count() - } - - pub(crate) fn origin_attempt_ids(&self) -> impl Iterator + '_ { - self.terminals.iter().map(|attempt| attempt.id) - } - - #[cfg(test)] - pub(crate) fn attempt_history(&self) -> impl Iterator>> { - self.history.iter() - } -} - -impl TypedQueryStore { - pub(crate) fn get_secondary_with_handle( - &self, - key: &F::SecondaryKey, - ) -> Option<(&F::Record, TerminalHandle)> { - self.terminals - .iter() - .find(|attempt| { - attempt - .record() - .is_some_and(|record| F::matches_secondary(record, key)) - }) - .map(|attempt| { - ( - attempt.record().unwrap(), - TerminalHandle { - attempt: attempt.clone(), - }, - ) - }) - } -} - -impl TypedQueryStore { - pub(crate) fn publish_selected_equivalent( - &mut self, - graph: &mut QueryGraph, - key: F::Key, - secondary: &F::SecondaryKey, - dependencies: impl IntoIterator, - ) -> Option - where - F: 'static, - { - let (record, origin) = self.get_secondary_with_handle(secondary)?; - if !origin.is_valid(graph) { - return None; - } - let record = F::rekey_equivalent(record, key)?; - let dependencies = dependencies - .into_iter() - .chain(std::iter::once(origin.observed())); - self.publish_selected_attempt( - graph, - record.clone(), - dependencies, - QueryStructuralWork::None, - AttemptExecution::Adopted, - ) - .expect("equivalent publication owns an active computation"); - Some(record) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Debug, Clone)] - struct TestRecord { - key: u32, - artifact_revision: u32, - terminal: TerminalKind, - } - - #[derive(Debug)] - struct TestFamily; - - impl TypedQueryFamily for TestFamily { - type Key = u32; - type Record = TestRecord; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(record: &Self::Record) -> TerminalKind { - record.terminal - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.artifact_revision == right.artifact_revision - } - - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.key % 2 == right.key % 2 - } - - fn record_is_consistent(record: &Self::Record) -> bool { - record.key == record.artifact_revision - } - } - - #[derive(Debug)] - struct SingleEntryFamily; - - impl TypedQueryFamily for SingleEntryFamily { - type Key = u32; - type Record = TestRecord; - const MAX_TERMINALS: usize = 1; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(record: &Self::Record) -> TerminalKind { - record.terminal - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.artifact_revision == right.artifact_revision - } - - fn diagnostics_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.key % 2 == right.key % 2 - } - - fn record_is_consistent(record: &Self::Record) -> bool { - record.key == record.artifact_revision - } - } - - fn record(key: u32, terminal: TerminalKind) -> TestRecord { - TestRecord { - key, - artifact_revision: key, - terminal, - } - } - - #[test] - fn success_and_failure_receive_typed_terminal_stamps() { - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - assert_eq!( - store - .insert_with_dependencies(&mut graph, record(1, TerminalKind::Success), []) - .stamp(), - TerminalStamp { - outcome: OutcomePublication::Success(0), - diagnostics: 1, - } - ); - assert_eq!( - store - .insert_with_dependencies(&mut graph, record(2, TerminalKind::Failure), []) - .stamp(), - TerminalStamp { - outcome: OutcomePublication::Failure(2), - diagnostics: 3, - } - ); - assert!(matches!( - store.handle(&1).unwrap().stamp().outcome, - OutcomePublication::Success(_) - )); - assert!(Arc::ptr_eq( - store.handle(&1).unwrap().attempt(), - store.attempt_history().next().unwrap() - )); - assert!(matches!( - store.handle(&2).unwrap().stamp().outcome, - OutcomePublication::Failure(_) - )); - } - - #[test] - fn equal_success_and_failure_preserve_outcome_and_diagnostic_identity() { - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - let success = - store.insert_with_dependencies(&mut graph, record(4, TerminalKind::Success), []); - assert_eq!( - store - .insert_with_dependencies(&mut graph, record(4, TerminalKind::Success), []) - .stamp(), - success.stamp() - ); - let failure = - store.insert_with_dependencies(&mut graph, record(5, TerminalKind::Failure), []); - assert_eq!( - store - .insert_with_dependencies(&mut graph, record(5, TerminalKind::Failure), []) - .stamp(), - failure.stamp() - ); - - let changed_variant = - store.insert_with_dependencies(&mut graph, record(5, TerminalKind::Success), []); - assert_ne!(changed_variant.stamp().outcome, failure.stamp().outcome); - assert_eq!( - changed_variant.stamp().diagnostics, - failure.stamp().diagnostics - ); - } - - #[test] - fn retention_evicts_oldest_terminal_attempt() { - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - for key in 0..=QUERY_TERMINAL_RETENTION_LIMIT as u32 { - store.insert_with_dependencies(&mut graph, record(key, TerminalKind::Success), []); - } - assert!(store.get(&0).is_none()); - assert!(store.get(&1).is_some()); - assert_eq!(store.len(), QUERY_TERMINAL_RETENTION_LIMIT); - assert_eq!(store.evictions(), 1); - } - - #[test] - fn selected_state_separates_current_last_good_and_cancellation() { - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - store.select(&mut graph, 7); - assert!(matches!( - store.selected_state(&graph), - Some(SelectedQueryState::Unpublished { key: 7 }) - )); - store.begin_selected(&mut graph, &7, 41).unwrap(); - store - .publish_selected(&mut graph, record(7, TerminalKind::Success), []) - .unwrap(); - assert!(store.selected_record(&graph).is_some()); - assert!(store.last_good_record().is_some()); - - store.select(&mut graph, 8); - store.begin_selected(&mut graph, &8, 42).unwrap(); - store - .publish_selected(&mut graph, record(8, TerminalKind::Failure), []) - .unwrap(); - assert!(matches!( - store.selected_state(&graph), - Some(SelectedQueryState::Failure { .. }) - )); - assert_eq!(store.last_good_record().unwrap().key, 7); - - store.select(&mut graph, 9); - store.begin_selected(&mut graph, &9, 43).unwrap(); - assert_eq!( - store.begin_selected(&mut graph, &9, 44), - Err(BeginSelectedError::DuplicateInFlight) - ); - store.cancel_selected(&mut graph, 43); - assert!(matches!( - store.selected_state(&graph), - Some(SelectedQueryState::Dirty { key: 9, .. }) - )); - assert_eq!(store.last_good_record().unwrap().key, 7); - let aborted = store - .attempt_history() - .filter_map(|attempt| attempt.aborted_reason()) - .collect::>(); - assert_eq!(aborted.len(), 2); - assert_eq!(aborted[0], AbortedQueryReason::DuplicateInFlight); - assert_eq!(aborted[1], AbortedQueryReason::Canceled); - - assert!(store.request_selected(&mut graph, 9, 45).unwrap().is_none()); - store - .publish_selected(&mut graph, record(9, TerminalKind::Success), []) - .unwrap(); - assert!(matches!( - store.selected_state(&graph), - Some(SelectedQueryState::Success { key: 9, .. }) - )); - } - - #[test] - fn nested_reentry_records_cycle_without_publishing_a_terminal_and_recovers() { - #[derive(Debug)] - struct NestedFamily; - - impl TypedQueryFamily for NestedFamily { - type Key = u32; - type Record = TestRecord; - const MAX_TERMINALS: usize = QUERY_TERMINAL_RETENTION_LIMIT; - - fn key(record: &Self::Record) -> &Self::Key { - &record.key - } - - fn terminal_kind(record: &Self::Record) -> TerminalKind { - record.terminal - } - - fn outcome_equal(left: &Self::Record, right: &Self::Record) -> bool { - left.artifact_revision == right.artifact_revision - } - - fn diagnostics_equal(_left: &Self::Record, _right: &Self::Record) -> bool { - true - } - - fn record_is_consistent(record: &Self::Record) -> bool { - record.key == record.artifact_revision - } - } - - let mut outer = TypedQueryStore::::default(); - let mut nested = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - assert!(outer.request_selected(&mut graph, 1, 1).unwrap().is_none()); - assert!(nested.request_selected(&mut graph, 2, 2).unwrap().is_none()); - assert!(matches!( - outer.request_selected(&mut graph, 1, 3), - Err(BeginSelectedError::DependencyCycle) - )); - assert!(outer.get(&1).is_none()); - assert_eq!( - outer - .attempt_history() - .find_map(|attempt| attempt.aborted_reason()), - Some(AbortedQueryReason::DependencyCycle) - ); - - let aborted = nested.publish_selected(&mut graph, record(2, TerminalKind::Success), []); - assert!(matches!(aborted, Err(BeginSelectedError::DependencyCycle))); - assert!(matches!( - outer.selected_state(&graph), - Some(SelectedQueryState::Dirty { .. }) - )); - assert!(matches!( - nested.selected_state(&graph), - Some(SelectedQueryState::Dirty { .. }) - )); - assert_eq!( - nested - .attempt_history() - .find_map(|attempt| attempt.aborted_reason()), - Some(AbortedQueryReason::DependencyCycle) - ); - assert!(outer.request_selected(&mut graph, 1, 4).unwrap().is_none()); - outer - .publish_selected(&mut graph, record(1, TerminalKind::Success), []) - .unwrap(); - assert!(outer.get(&1).is_some()); - } - - #[test] - fn evicted_artifacts_leave_bounded_validation_tombstones_and_republish_fresh() { - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - let first = - store.insert_with_dependencies(&mut graph, record(0, TerminalKind::Success), []); - for key in 1..=QUERY_TERMINAL_RETENTION_LIMIT as u32 { - store.insert_with_dependencies(&mut graph, record(key, TerminalKind::Success), []); - } - let retention = store.retention(&graph); - assert_eq!(retention.retained, QUERY_TERMINAL_RETENTION_LIMIT); - assert_eq!(retention.tombstones, 1); - assert_eq!(retention.evictions, 1); - - assert!(store.request_selected(&mut graph, 0, 1).unwrap().is_none()); - let republished = store - .publish_selected(&mut graph, record(0, TerminalKind::Success), []) - .unwrap(); - assert_ne!(first.stamp(), republished.stamp()); - assert!(republished.is_valid(&mut graph)); - assert_eq!(store.retention(&graph).tombstones, 1); - } - - #[test] - fn popped_tombstone_transfers_ownership_to_graph_until_reverse_pin_retires() { - #[derive(Debug)] - struct DependentFamily; - - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - let first = - store.insert_with_dependencies(&mut graph, record(0, TerminalKind::Success), []); - let dependent = TypedNode::::allocate(&mut graph); - graph.publish::( - dependent.id(), - DependencyStamp::Terminal(TerminalStamp { - outcome: OutcomePublication::Success(0), - diagnostics: 0, - }), - [first.observed()], - ); - - store.insert_with_dependencies(&mut graph, record(1, TerminalKind::Success), []); - assert_eq!(store.retention(&graph).tombstones, 1); - assert_eq!(graph.retained_disappeared_count(), 0); - - store.insert_with_dependencies(&mut graph, record(2, TerminalKind::Success), []); - assert_eq!(store.retention(&graph).tombstones, 1); - assert_eq!(graph.retained_disappeared_count(), 1); - - graph.retire::(dependent.id()); - assert_eq!(graph.retained_disappeared_count(), 0); +/// Immutable lifecycle view used by metrics and the diagnostic attempt index. +pub(crate) trait AttemptView: std::fmt::Debug + Send + Sync { + fn id(&self) -> AttemptId; + fn execution(&self) -> AttemptExecution; + fn outcome(&self) -> AttemptOutcomeKind; + fn origin_id(&self) -> AttemptId; + fn runtime_observations(&self) -> &[RuntimeObservation] { + &[] } - - #[test] - #[should_panic(expected = "typed query record key does not match")] - fn insertion_rejects_artifact_from_another_revision() { - let mut store = TypedQueryStore::::default(); - let mut graph = QueryGraph::default(); - store.insert_with_dependencies( - &mut graph, - TestRecord { - key: 1, - artifact_revision: 2, - terminal: TerminalKind::Success, - }, - [], - ); + fn runtime_work(&self) -> &[(Arc, u64)] { + &[] } + fn work(&self) -> &QueryStructuralWork; + fn diagnostics(&self) -> Option<&Arc>; } diff --git a/crates/rue-compiler/src/unstable.rs b/crates/rue-compiler/src/unstable.rs index c35790f99..cea9d3588 100644 --- a/crates/rue-compiler/src/unstable.rs +++ b/crates/rue-compiler/src/unstable.rs @@ -5,7 +5,6 @@ //! installed into a session or used as query keys. use serde_json::{Value, json}; -use sha2::{Digest, Sha256}; use std::fmt::Write as _; use std::sync::Arc; @@ -43,6 +42,17 @@ pub fn import_demand_frontier_for_roots( session.import_demand_frontier_for_roots(revision, plan, mode, roots) } +/// Stages the exact input revision currently owned by the compiler. +/// +/// The host supplies only the opaque revision returned by +/// [`begin_import_input_request`] or [`publish_import_observation_batch`]. +pub fn stage_import_input_request( + session: &mut crate::CompilerSession, + revision: ImportInputRevision, +) -> Result { + session.stage_import_input_request(revision) +} + /// The demand roots for a trusted-toolchain successor plan's delta occurrences /// alone (RUE-1112). Derived directly from the plan's delta segment, so the host /// roots its re-close frontier without materializing or filtering the merged @@ -68,6 +78,15 @@ pub fn import_observation_ledger( session.import_observation_ledger(revision) } +/// Closes the exact compiler-published input revision after its rooted +/// frontier is exhausted. +pub fn close_import_input_request( + session: &mut crate::CompilerSession, + revision: ImportInputRevision, +) -> Result, crate::CompileErrors> { + session.close_import_input_request(revision) +} + /// Stage a strictly-additive trusted-toolchain successor (RUE-1112). The staged /// snapshot, context, provenance, and carried ledger are the CURRENT /// compiler-published view's own state and the module delta is derived from the @@ -505,10 +524,6 @@ pub fn semantic_input_debug(view: &crate::SemanticView) -> String { view.owner().unstable_input_debug() } -pub fn semantic_durable_artifact_status(view: &crate::SemanticView) -> DurableArtifactStatus { - view.owner().unstable_durable_artifact_status() -} - /// Exact, owned semantic-state projection used by in-tree cold-vs-reused /// differential tooling. Its contents and formatting are deliberately /// unstable and are not an artifact that can be fed back into the compiler. @@ -548,12 +563,13 @@ pub fn into_oracle_semantic_state( ) -> Result { let semantic = Arc::try_unwrap(semantic).map_err(|_| "semantic view is still shared")?; let (semantic_owner, rir_owner) = semantic.into_owners(); - let rir_owner = Arc::try_unwrap(rir_owner).map_err(|_| "RIR owner is still shared")?; + drop(rir_owner); let semantic_owner = Arc::try_unwrap(semantic_owner).map_err(|_| "semantic owner is still shared")?; + let (rir_owner, functions, type_pool, strings, _) = semantic_owner.into_parts_with_rir(); + let rir_owner = Arc::try_unwrap(rir_owner).map_err(|_| "RIR owner is still shared")?; let rir_payload_storage_stats = rir_owner.rir().payload_storage_stats(); let (_, symbols) = rir_owner.into_parts(); - let (functions, type_pool, strings, _) = semantic_owner.into_parts(); Ok(OracleSemanticState { interner: symbols.into_interner(), functions: functions @@ -669,9 +685,7 @@ impl crate::CompilerSession { } stage => { let semantic = self.canonical_semantic(request.options)?; - let rir = self - .selected_semantic_rir_owner() - .expect("successful semantic query retains canonical RIR"); + let rir = semantic.rir_owner().clone(); let interner = rir.semantic_symbols().interner(); let backend_request = match stage { PresentationStage::Lowering => Some(rue_codegen::BackendArtifactRequest { @@ -1150,145 +1164,6 @@ mod codegen_unit_tests { } } -/// Opaque equality status for session-owned durable artifacts. -/// -/// The fingerprint is instrumentation, not a cache key or durable schema. It -/// deliberately has no import/install operation. -#[derive(Clone, PartialEq, Eq)] -pub struct DurableArtifactStatus { - count: usize, - fingerprint: [u8; 32], -} - -impl std::fmt::Debug for DurableArtifactStatus { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter - .debug_struct("DurableArtifactStatus") - .field("count", &self.count) - .finish_non_exhaustive() - } -} - -/// Owned dependency-manifest counters. No manifest records or cache payloads -/// are retained by this snapshot. -#[derive(Debug, Clone, PartialEq)] -pub struct DependencyManifestMetrics { - pub body_owner_events_translated: usize, - pub body_named_events_translated: usize, - pub body_dependency_records_built: usize, - pub extra_rir_instructions_visited: usize, - pub durable_bodies: DurableBodyMetrics, -} - -/// Durable-body reuse counters without the durable cache records themselves. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)] -pub struct DurableBodyMetrics { - pub candidate_comparisons: usize, - pub candidate_fallbacks: usize, - pub specialized_mapping_attempts: usize, - pub specialized_mapping_successes: usize, - pub specialized_mapping_failures: usize, - pub export_attempts: usize, - pub export_successes: usize, - pub export_rejections: usize, - pub instructions_exported: usize, - pub places_exported: usize, - pub strings_exported: usize, - pub conversion_attempts: usize, - pub conversion_completions: usize, - pub conversion_failures: usize, - pub stable_key_joins: usize, - pub finalization_attempts: usize, - pub finalization_completions: usize, - pub finalization_failures: usize, - pub projection_attempts: usize, - pub projection_completions: usize, - pub projection_failures: usize, - pub instructions_projected: usize, - pub places_projected: usize, - pub strings_projected: usize, - pub import_attempts: usize, - pub import_successes: usize, - pub import_failures: usize, - pub installed_instructions: usize, - pub installed_places: usize, - pub installed_strings: usize, - pub atomic_discards: usize, - pub reused_bodies: usize, - pub skipped_body_analyses: usize, -} - -impl From for DurableBodyMetrics { - fn from(work: crate::DurableBodyWork) -> Self { - Self { - candidate_comparisons: work.candidate_comparisons, - candidate_fallbacks: work.candidate_fallbacks, - specialized_mapping_attempts: work.specialized_mapping_attempts, - specialized_mapping_successes: work.specialized_mapping_successes, - specialized_mapping_failures: work.specialized_mapping_failures, - export_attempts: work.export_attempts, - export_successes: work.export_successes, - export_rejections: work.export_rejections, - instructions_exported: work.instructions_exported, - places_exported: work.places_exported, - strings_exported: work.strings_exported, - conversion_attempts: work.conversion_attempts, - conversion_completions: work.conversion_completions, - conversion_failures: work.conversion_failures, - stable_key_joins: work.stable_key_joins, - finalization_attempts: work.finalization_attempts, - finalization_completions: work.finalization_completions, - finalization_failures: work.finalization_failures, - projection_attempts: work.projection_attempts, - projection_completions: work.projection_completions, - projection_failures: work.projection_failures, - instructions_projected: work.instructions_projected, - places_projected: work.places_projected, - strings_projected: work.strings_projected, - import_attempts: work.import_attempts, - import_successes: work.import_successes, - import_failures: work.import_failures, - installed_instructions: work.installed_instructions, - installed_places: work.installed_places, - installed_strings: work.installed_strings, - atomic_discards: work.atomic_discards, - reused_bodies: work.reused_bodies, - skipped_body_analyses: work.skipped_body_analyses, - } - } -} - -impl DependencyManifestMetrics { - pub(crate) fn from_work(work: crate::session::SemanticDependencyManifestWork) -> Self { - let durable = work.durable_bodies; - Self { - body_owner_events_translated: work.body_owner_events_translated, - body_named_events_translated: work.body_named_events_translated, - body_dependency_records_built: work.body_dependency_records_built, - extra_rir_instructions_visited: work.extra_rir_instructions_visited, - durable_bodies: durable.into(), - } - } -} - -impl DurableArtifactStatus { - pub fn count(&self) -> usize { - self.count - } - - pub(crate) fn from_debug(artifacts: &[T]) -> Self { - let mut hasher = Sha256::new(); - for artifact in artifacts { - hasher.update(format!("{artifact:?}").as_bytes()); - hasher.update([0]); - } - Self { - count: artifacts.len(), - fingerprint: hasher.finalize().into(), - } - } -} - /// Query lifecycle counters contained in [`MetricsSnapshot`]. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct QueryMetrics { @@ -1313,8 +1188,6 @@ pub struct RetentionMetrics { pub diagnostic_entries: usize, pub diagnostic_source_attempts: usize, pub diagnostic_source_bytes: usize, - pub dependency_manifests: usize, - pub invalidation_plans: usize, } /// Merge counters used by the in-tree benchmark. @@ -1488,12 +1361,6 @@ impl MetricsSnapshot { pub fn definitions(&self) -> QueryMetrics { self.inner.definitions.into() } - pub fn dependency_manifests(&self) -> QueryMetrics { - self.inner.dependency_manifests.into() - } - pub fn invalidation_plans(&self) -> QueryMetrics { - self.inner.invalidation_plans.into() - } pub fn downstream_invalidations(&self) -> usize { self.inner.downstream_invalidations } @@ -1548,8 +1415,6 @@ impl MetricsSnapshot { diagnostic_entries: self.inner.retention.diagnostic_entries, diagnostic_source_attempts: self.inner.retention.diagnostic_source_attempts, diagnostic_source_bytes: self.inner.retention.diagnostic_source_bytes, - dependency_manifests: self.inner.retention.dependency_manifests, - invalidation_plans: self.inner.retention.invalidation_plans, } } pub fn semantic_work_json(&self, from: usize) -> Value { @@ -1600,30 +1465,13 @@ fn semantic_work_json(work: &crate::session::CompilerSessionWork, from: usize) - "specialized_bodies_succeeded": records.iter().map(|record| record.work.body_analysis.specialized_bodies_succeeded).sum::(), "specialized_bodies_failed": records.iter().map(|record| record.work.body_analysis.specialized_bodies_failed).sum::(), "durable_bodies": { - "candidate_comparisons": records.iter().map(|record| record.work.durable_bodies.candidate_comparisons).sum::(), "candidate_fallbacks": records.iter().map(|record| record.work.durable_bodies.candidate_fallbacks).sum::(), - "specialized_mapping_attempts": records.iter().map(|record| record.work.durable_bodies.specialized_mapping_attempts).sum::(), - "specialized_mapping_successes": records.iter().map(|record| record.work.durable_bodies.specialized_mapping_successes).sum::(), - "specialized_mapping_failures": records.iter().map(|record| record.work.durable_bodies.specialized_mapping_failures).sum::(), "export_attempts": records.iter().map(|record| record.work.durable_bodies.export_attempts).sum::(), "export_successes": records.iter().map(|record| record.work.durable_bodies.export_successes).sum::(), "export_rejections": records.iter().map(|record| record.work.durable_bodies.export_rejections).sum::(), "instructions_exported": records.iter().map(|record| record.work.durable_bodies.instructions_exported).sum::(), "places_exported": records.iter().map(|record| record.work.durable_bodies.places_exported).sum::(), "strings_exported": records.iter().map(|record| record.work.durable_bodies.strings_exported).sum::(), - "conversion_attempts": records.iter().map(|record| record.work.durable_bodies.conversion_attempts).sum::(), - "conversion_completions": records.iter().map(|record| record.work.durable_bodies.conversion_completions).sum::(), - "conversion_failures": records.iter().map(|record| record.work.durable_bodies.conversion_failures).sum::(), - "finalization_attempts": records.iter().map(|record| record.work.durable_bodies.finalization_attempts).sum::(), - "finalization_completions": records.iter().map(|record| record.work.durable_bodies.finalization_completions).sum::(), - "finalization_failures": records.iter().map(|record| record.work.durable_bodies.finalization_failures).sum::(), - "stable_key_joins": records.iter().map(|record| record.work.durable_bodies.stable_key_joins).sum::(), - "projection_attempts": records.iter().map(|record| record.work.durable_bodies.projection_attempts).sum::(), - "projection_completions": records.iter().map(|record| record.work.durable_bodies.projection_completions).sum::(), - "projection_failures": records.iter().map(|record| record.work.durable_bodies.projection_failures).sum::(), - "instructions_projected": records.iter().map(|record| record.work.durable_bodies.instructions_projected).sum::(), - "places_projected": records.iter().map(|record| record.work.durable_bodies.places_projected).sum::(), - "strings_projected": records.iter().map(|record| record.work.durable_bodies.strings_projected).sum::(), "import_attempts": records.iter().map(|record| record.work.durable_bodies.import_attempts).sum::(), "import_successes": records.iter().map(|record| record.work.durable_bodies.import_successes).sum::(), "import_failures": records.iter().map(|record| record.work.durable_bodies.import_failures).sum::(), @@ -1714,285 +1562,3 @@ pub enum DifferentialOracleFault { Diagnostic, Import, } - -/// Opaque dependency baseline used only by unstable invalidation benchmarks. -#[derive(Debug, Clone)] -pub struct DependencyBaseline { - pub(crate) inner: std::sync::Arc, - owner: Arc<()>, -} - -impl DependencyBaseline { - pub(crate) fn new( - inner: std::sync::Arc, - owner: Arc<()>, - ) -> Self { - Self { inner, owner } - } - pub(crate) fn belongs_to(&self, owner: &Arc<()>) -> bool { - Arc::ptr_eq(&self.owner, owner) - } - pub fn unstable_metrics(&self) -> DependencyManifestMetrics { - self.inner.unstable_metrics() - } - pub fn unstable_durable_artifact_status(&self) -> DurableArtifactStatus { - self.inner.unstable_durable_artifact_status() - } - pub fn input(&self) -> String { - format!("{:?}", self.inner.input()) - } - pub fn imports(&self) -> String { - format!("{:?}", self.inner.imports()) - } - pub fn definitions(&self) -> String { - format!("{:?}", self.inner.definitions()) - } - pub fn definition_fingerprints(&self) -> String { - format!("{:?}", self.inner.definition_fingerprints()) - } - pub fn module_imports(&self) -> String { - format!("{:?}", self.inner.module_imports()) - } - pub fn free_function_dependencies(&self) -> String { - format!("{:?}", self.inner.free_function_dependencies()) - } - pub fn implicit_named_destructor_dependencies(&self) -> String { - format!("{:?}", self.inner.implicit_named_destructor_dependencies()) - } - pub fn declaration_type_dependencies(&self) -> String { - format!("{:?}", self.inner.declaration_type_dependencies()) - } - pub fn declaration_type_call_head_dependencies(&self) -> String { - format!("{:?}", self.inner.declaration_type_call_head_dependencies()) - } - pub fn builtin_type_call_head_inputs(&self) -> String { - format!("{:?}", self.inner.builtin_type_call_head_inputs()) - } - pub fn named_const_dependencies(&self) -> String { - format!("{:?}", self.inner.named_const_dependencies()) - } - pub fn body_dependencies(&self) -> String { - format!("{:?}", self.inner.body_dependencies()) - } - pub fn body_dependency_blockers(&self) -> String { - format!("{:?}", self.inner.body_dependency_blockers()) - } - pub fn dependency_blockers(&self) -> String { - format!("{:?}", self.inner.dependency_blockers()) - } - pub fn free_function_caller_dependencies_complete(&self) -> bool { - self.inner.free_function_caller_dependencies_complete() - } - pub fn implicit_named_destructor_dependencies_complete(&self) -> bool { - self.inner.implicit_named_destructor_dependencies_complete() - } - pub fn declaration_type_dependencies_complete(&self) -> bool { - self.inner.declaration_type_dependencies_complete() - } - pub fn declaration_type_call_head_dependencies_complete(&self) -> bool { - self.inner - .declaration_type_call_head_dependencies_complete() - } - pub fn supported_type_call_heads_complete(&self) -> bool { - self.inner.supported_type_call_heads_complete() - } - pub fn named_value_const_dependencies_complete(&self) -> bool { - self.inner.named_value_const_dependencies_complete() - } - pub fn semantic_dependency_graph_complete(&self) -> bool { - self.inner.semantic_dependency_graph_complete() - } - pub fn definition_universe_complete(&self) -> bool { - self.inner.definition_universe_complete() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DependencySurface { - BodyOwner, - FreeFunctionCall, - GenericNamedMethodCall, - ImplicitNamedDestructor, - DeclarationType, - DeclarationTypeCallHead, - SupportedTypeCallHead, - NamedValueConst, -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DependencyIncompleteReason { - AnonymousBodyOwnerUnavailable, - CallerEndpointUnavailable, - GenericSubstitutionIdentityUnavailable, - AnonymousDropOwnerUnavailable, - ResolvedTypeIdentityUnavailable, - TypeCallHeadIdentityUnavailable, - UnsupportedDynamicTypeCallHead, - ConstEndpointUnavailable, -} -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DependencyBlocker { - owner: Option, - surface: DependencySurface, - reason: DependencyIncompleteReason, -} -impl DependencyBlocker { - pub fn owner(&self) -> Option<&str> { - self.owner.as_deref() - } - pub fn surface(&self) -> DependencySurface { - self.surface - } - pub fn reason(&self) -> DependencyIncompleteReason { - self.reason - } -} -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum FullInvalidationReason { - RootChanged, - ModuleImportsChanged, - TargetChanged, - PreviewFeaturesChanged, - IncompleteDefinitionUniverse, - IncompleteDependencyGraph(std::sync::Arc<[DependencyBlocker]>), -} -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum InvalidationScope { - Full { - reasons: std::sync::Arc<[FullInvalidationReason]>, - }, - Incremental, -} -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct InvalidationWorkMetrics { - pub definition_fingerprints_compared: usize, - pub dependency_edges_visited: usize, - pub reverse_closure_nodes_visited: usize, - pub extra_rir_instructions_visited: usize, -} -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InvalidationMetrics { - scope: InvalidationScope, - added: Vec<()>, - removed: Vec<()>, - changed: Vec<()>, - invalidated: Vec<()>, - reusable: Vec<()>, - work: InvalidationWorkMetrics, -} -impl InvalidationMetrics { - pub(crate) fn from_plan(plan: &crate::SemanticInvalidationPlan) -> Self { - fn surface(value: crate::SemanticDependencySurface) -> DependencySurface { - use crate::SemanticDependencySurface as S; - match value { - S::BodyOwner => DependencySurface::BodyOwner, - S::FreeFunctionCall => DependencySurface::FreeFunctionCall, - S::GenericNamedMethodCall => DependencySurface::GenericNamedMethodCall, - S::ImplicitNamedDestructor => DependencySurface::ImplicitNamedDestructor, - S::DeclarationType => DependencySurface::DeclarationType, - S::DeclarationTypeCallHead => DependencySurface::DeclarationTypeCallHead, - S::SupportedTypeCallHead => DependencySurface::SupportedTypeCallHead, - S::NamedValueConst => DependencySurface::NamedValueConst, - } - } - fn reason(value: crate::SemanticDependencyIncompleteReason) -> DependencyIncompleteReason { - use crate::SemanticDependencyIncompleteReason as R; - match value { - R::AnonymousBodyOwnerUnavailable => { - DependencyIncompleteReason::AnonymousBodyOwnerUnavailable - } - R::CallerEndpointUnavailable => { - DependencyIncompleteReason::CallerEndpointUnavailable - } - R::GenericSubstitutionIdentityUnavailable => { - DependencyIncompleteReason::GenericSubstitutionIdentityUnavailable - } - R::AnonymousDropOwnerUnavailable => { - DependencyIncompleteReason::AnonymousDropOwnerUnavailable - } - R::ResolvedTypeIdentityUnavailable => { - DependencyIncompleteReason::ResolvedTypeIdentityUnavailable - } - R::TypeCallHeadIdentityUnavailable => { - DependencyIncompleteReason::TypeCallHeadIdentityUnavailable - } - R::UnsupportedDynamicTypeCallHead => { - DependencyIncompleteReason::UnsupportedDynamicTypeCallHead - } - R::ConstEndpointUnavailable => DependencyIncompleteReason::ConstEndpointUnavailable, - } - } - let scope = match plan.scope() { - crate::SemanticInvalidationScope::Incremental => InvalidationScope::Incremental, - crate::SemanticInvalidationScope::Full { reasons } => InvalidationScope::Full { - reasons: reasons - .iter() - .map(|value| match value { - crate::SemanticFullInvalidationReason::RootChanged => { - FullInvalidationReason::RootChanged - } - crate::SemanticFullInvalidationReason::ModuleImportsChanged => { - FullInvalidationReason::ModuleImportsChanged - } - crate::SemanticFullInvalidationReason::TargetChanged => { - FullInvalidationReason::TargetChanged - } - crate::SemanticFullInvalidationReason::PreviewFeaturesChanged => { - FullInvalidationReason::PreviewFeaturesChanged - } - crate::SemanticFullInvalidationReason::IncompleteDefinitionUniverse => { - FullInvalidationReason::IncompleteDefinitionUniverse - } - crate::SemanticFullInvalidationReason::IncompleteDependencyGraph( - blockers, - ) => FullInvalidationReason::IncompleteDependencyGraph( - blockers - .iter() - .map(|b| DependencyBlocker { - owner: b.owner().map(|o| o.name().to_owned()), - surface: surface(b.surface()), - reason: reason(b.reason()), - }) - .collect(), - ), - }) - .collect(), - }, - }; - let work = plan.work(); - Self { - scope, - added: vec![(); plan.added().len()], - removed: vec![(); plan.removed().len()], - changed: vec![(); plan.changed().len()], - invalidated: vec![(); plan.invalidated().len()], - reusable: vec![(); plan.reusable().len()], - work: InvalidationWorkMetrics { - definition_fingerprints_compared: work.definition_fingerprints_compared, - dependency_edges_visited: work.dependency_edges_visited, - reverse_closure_nodes_visited: work.reverse_closure_nodes_visited, - extra_rir_instructions_visited: work.extra_rir_instructions_visited, - }, - } - } - pub fn scope(&self) -> &InvalidationScope { - &self.scope - } - pub fn added(&self) -> &[()] { - &self.added - } - pub fn removed(&self) -> &[()] { - &self.removed - } - pub fn changed(&self) -> &[()] { - &self.changed - } - pub fn invalidated(&self) -> &[()] { - &self.invalidated - } - pub fn reusable(&self) -> &[()] { - &self.reusable - } - pub fn work(&self) -> InvalidationWorkMetrics { - self.work - } -} diff --git a/crates/rue-compiler/tests/differential_oracle.rs b/crates/rue-compiler/tests/differential_oracle.rs index a876ba1bc..bfd4925b6 100644 --- a/crates/rue-compiler/tests/differential_oracle.rs +++ b/crates/rue-compiler/tests/differential_oracle.rs @@ -13,11 +13,11 @@ use std::{collections::HashMap, fmt::Write as _, sync::Arc}; use rue_cfg::OptLevel; use rue_compiler::unstable::{ DifferentialOracleFault, DiscoverySourceAssembler, ImportDemandMode, PresentationRequest, - PresentationStage, begin_import_input_request, discovery_attempt, + PresentationStage, begin_import_input_request, close_import_input_request, discovery_attempt, import_demand_frontier_for_roots, import_discovery_accepted_reads_debug, import_discovery_graph_input_debug, import_discovery_observation_ledger_debug, - import_observation_ledger, inject_stale_query_for_oracle, oracle_executable, - publish_import_observation_batch, semantic_input_debug, + inject_stale_query_for_oracle, oracle_executable, publish_import_observation_batch, + semantic_input_debug, stage_import_input_request, }; use rue_compiler::{ AcceptedImportSource, CompileOptions, CompilerSession, FileMetadataFingerprint, @@ -52,7 +52,6 @@ struct Observation { semantic_hash: String, executable_hash: String, identities: String, - manifest: String, imports: String, } @@ -145,13 +144,7 @@ fn close_discovery(session: &mut CompilerSession, step: &Step) -> String { ) .unwrap(); loop { - let ledger = import_observation_ledger(session, revision).unwrap(); - let plan = match session.stage_import_discovery( - &step.snapshot, - discovery.context.clone(), - discovery.accepted_reads.shared_slice(), - ledger.clone(), - ) { + let plan = match stage_import_input_request(session, revision) { Ok(plan) => plan, Err(errors) => return format!("stage-error:{errors:?}"), }; @@ -164,7 +157,7 @@ fn close_discovery(session: &mut CompilerSession, step: &Step) -> String { ) .unwrap(); if frontier.requests().is_empty() { - return match session.close_import_discovery(ledger) { + return match close_import_input_request(session, revision) { Ok(artifact) => render_import_discovery(&artifact), Err(errors) => format!("close-error:{errors:?}"), }; @@ -350,29 +343,7 @@ fn observe_with_fault( DifferentialOracleFault::Diagnostic )); } - // Capture the semantic request's selected batch before the manifest query - // performs its own supporting diagnostic work. let diagnostics = normalize_diagnostics(session.latest_diagnostics()); - let manifest = match session.unstable_dependency_baseline(&step.options, None) { - Ok(manifest) => format!( - "input={:?};imports={:?};definitions={:?};fingerprints={:?};module-imports={:?};free={:?};implicit={:?};decl-types={:?};call-heads={:?};builtins={:?};consts={:?};bodies={:?};blockers={:?};complete={}", - manifest.input(), - manifest.imports(), - manifest.definitions(), - manifest.definition_fingerprints(), - manifest.module_imports(), - manifest.free_function_dependencies(), - manifest.implicit_named_destructor_dependencies(), - manifest.declaration_type_dependencies(), - manifest.declaration_type_call_head_dependencies(), - manifest.builtin_type_call_head_inputs(), - manifest.named_const_dependencies(), - manifest.body_dependencies(), - manifest.dependency_blockers(), - manifest.semantic_dependency_graph_complete(), - ), - Err(errors) => format!("error:{errors:?}"), - }; if fault == Some(DifferentialOracleFault::Import) { assert!(inject_stale_query_for_oracle( session, @@ -389,7 +360,6 @@ fn observe_with_fault( semantic_hash, executable_hash, identities, - manifest, imports, } } @@ -414,7 +384,6 @@ fn differing_fields(left: &Observation, right: &Observation) -> Vec<&'static str "executable-hash", ), (left.identities != right.identities, "stable-identities"), - (left.manifest != right.manifest, "dependency-manifest"), (left.imports != right.imports, "import-discovery"), ] .into_iter() @@ -640,17 +609,6 @@ fn corpus() -> Vec { fn bounded_corpus_matches_stepwise_fresh_sessions() { let corpus = corpus(); assert_equivalent(&corpus); - let incomplete = corpus - .iter() - .find(|step| step.name == "incomplete-manifest") - .unwrap(); - let mut fresh = CompilerSession::new(); - assert!( - observe(&mut fresh, incomplete) - .manifest - .contains("complete=false") - ); - let glue = corpus .iter() .find(|step| step.name == "query-native-drop-glue") @@ -668,7 +626,7 @@ fn bounded_corpus_matches_stepwise_fresh_sessions() { } #[test] -fn option_leaves_reuse_source_terminals_and_restore_exact_semantic_variants() { +fn option_variants_produce_independent_canonical_semantic_outputs() { let source = snapshot( &[( 7, @@ -697,16 +655,16 @@ fn option_leaves_reuse_source_terminals_and_restore_exact_semantic_variants() { preview_features: PreviewFeatures::from([PreviewFeature::TestInfra]), ..default.clone() }; - session.semantic(&target).unwrap(); - session.semantic(&optimized).unwrap(); - session.semantic(&preview).unwrap(); - - assert_eq!(session.unstable_metrics().merge().executions, 1); - assert_eq!(session.unstable_metrics().rir().executions, 1); - assert_eq!(session.unstable_metrics().semantic().executions, 4); - assert!(first.shares_owner(&session.semantic(&default).unwrap())); - assert_eq!(session.unstable_metrics().semantic().executions, 4); - assert_eq!(session.unstable_metrics().semantic().reuses, 1); + let variants = [ + first, + session.semantic(&target).unwrap(), + session.semantic(&optimized).unwrap(), + session.semantic(&preview).unwrap(), + ]; + for semantic in variants { + assert_eq!(semantic.function_views().next().unwrap().name(), "helper"); + assert_eq!(semantic.function_views().nth(1).unwrap().name(), "main"); + } } #[test] @@ -748,7 +706,6 @@ fn failure_recovery_and_bounded_eviction_match_fresh_sessions() { } let metrics = session.unstable_metrics(); assert!(metrics.retention().diagnostic_entries < steps.len()); - assert!(metrics.retention().invalidation_plans < steps.len()); } #[test] diff --git a/crates/rue-compiler/tests/public_api.rs b/crates/rue-compiler/tests/public_api.rs index 0520c7988..2abc41bc7 100644 --- a/crates/rue-compiler/tests/public_api.rs +++ b/crates/rue-compiler/tests/public_api.rs @@ -364,26 +364,6 @@ fn presentation_file_order_rejects_unknown_duplicate_and_incomplete_inputs() { } } -#[test] -fn dependency_baselines_cannot_cross_session_ownership() { - let snapshot = SourceSnapshot::single("main.rue", "fn main() -> i32 { 0 }").unwrap(); - let options = CompileOptions::default(); - let mut first = CompilerSession::new(); - let mut second = CompilerSession::new(); - first.update(&snapshot).into_result().unwrap(); - second.update(&snapshot).into_result().unwrap(); - let first_baseline = first.unstable_dependency_baseline(&options, None).unwrap(); - let second_baseline = second.unstable_dependency_baseline(&options, None).unwrap(); - let before = first.unstable_metrics().invalidation_plans(); - - assert!( - first - .unstable_invalidation_metrics(&first_baseline, &second_baseline) - .is_err() - ); - assert_eq!(first.unstable_metrics().invalidation_plans(), before); -} - #[test] fn oracle_consumption_accepts_only_the_semantic_views_embedded_rir_owner() { let bridge: fn(Arc) -> Result = diff --git a/crates/rue-oracle-diff/src/main.rs b/crates/rue-oracle-diff/src/main.rs index 6133bf029..24a27b147 100644 --- a/crates/rue-oracle-diff/src/main.rs +++ b/crates/rue-oracle-diff/src/main.rs @@ -49,7 +49,8 @@ mod trap; use rue_compiler::unstable::{ DiscoverySourceAssembler, ImportDemandMode, begin_import_input_request, - import_demand_frontier_for_roots, import_observation_ledger, publish_import_observation_batch, + close_import_input_request, import_demand_frontier_for_roots, import_observation_ledger, + publish_import_observation_batch, stage_import_input_request, }; use rue_compiler::{ AcceptedImportSource, CompilerSession, FileMetadataFingerprint, ImportDiscoveryContext, @@ -703,16 +704,9 @@ fn run_source_with_real_std( .map_err(|error| error.to_string())?; loop { - let snapshot = assembler.snapshot().map_err(|error| error.to_string())?; let ledger = import_observation_ledger(&session, revision).map_err(|error| error.to_string())?; - let plan = session - .stage_import_discovery( - &snapshot, - context.clone(), - assembler.accepted_read_manifest().shared_slice(), - ledger.clone(), - ) + let plan = stage_import_input_request(&mut session, revision) .map_err(|errors| format!("{errors:#?}"))?; let frontier = import_demand_frontier_for_roots( &mut session, @@ -723,8 +717,7 @@ fn run_source_with_real_std( ) .map_err(|error| error.to_string())?; if frontier.requests().is_empty() { - session - .close_import_discovery(ledger) + close_import_input_request(&mut session, revision) .map_err(|errors| format!("{errors:#?}"))?; return Ok(run_session_with_preview_features(session, preview_features)); } diff --git a/crates/rue-oracle/src/tests/call_contracts.rs b/crates/rue-oracle/src/tests/call_contracts.rs index 981556956..f6dedb91b 100644 --- a/crates/rue-oracle/src/tests/call_contracts.rs +++ b/crates/rue-oracle/src/tests/call_contracts.rs @@ -1113,8 +1113,8 @@ fn query_cfg_state_with_trusted_std( ) -> Result { use rue_compiler::unstable::{ DiscoverySourceAssembler, ImportDemandMode, begin_import_input_request, - import_demand_frontier_for_roots, import_observation_ledger, - publish_import_observation_batch, + close_import_input_request, import_demand_frontier_for_roots, import_observation_ledger, + publish_import_observation_batch, stage_import_input_request, }; use rue_compiler::{ AcceptedImportSource, CompilerSession, FileMetadataFingerprint, ImportDiscoveryContext, @@ -1150,15 +1150,8 @@ fn query_cfg_state_with_trusted_std( ) .expect("begin trusted std request"); loop { - let snapshot = assembler.snapshot().expect("trusted std snapshot"); let ledger = import_observation_ledger(&session, revision).expect("current std ledger"); - let plan = session - .stage_import_discovery( - &snapshot, - context.clone(), - assembler.accepted_read_manifest().shared_slice(), - ledger.clone(), - ) + let plan = stage_import_input_request(&mut session, revision) .expect("valid trusted std discovery plan"); let frontier = import_demand_frontier_for_roots( &mut session, @@ -1169,8 +1162,7 @@ fn query_cfg_state_with_trusted_std( ) .expect("trusted std frontier"); if frontier.requests().is_empty() { - session - .close_import_discovery(ledger) + close_import_input_request(&mut session, revision) .expect("close valid import discovery revision"); break; } diff --git a/crates/rue/src/main.rs b/crates/rue/src/main.rs index 6d6ba1ac8..abee1deb3 100644 --- a/crates/rue/src/main.rs +++ b/crates/rue/src/main.rs @@ -1779,8 +1779,6 @@ mod tests { assert_eq!(work.semantic.cfg.cfg_builds_failed, 0); let session_work = &frontend.session_work; assert_eq!(session_work.updates(), 1); - assert_eq!(session_work.merge().executions, 1); - assert_eq!(session_work.rir().executions, 1); assert_eq!(session_work.semantic().executions, 1); let mut presentation_session = CompilerSession::new(); diff --git a/crates/rue/src/source_loader.rs b/crates/rue/src/source_loader.rs index 5ac17ce5f..24d689131 100644 --- a/crates/rue/src/source_loader.rs +++ b/crates/rue/src/source_loader.rs @@ -11,10 +11,10 @@ use rue_compiler::unstable::frontend_query_invalidations; use rue_compiler::unstable::{ DiscoverySourceAssembler, ImportDemandFrontier, ImportDemandMode, ImportInputRevision, SemanticParkOutcome, TrustedSuccessorDelta, begin_import_input_request, - close_import_discovery_successor, closed_discovery_continuation, discovery_attempt, - import_demand_frontier_for_roots, import_observation_ledger, plan_delta_roots, - publish_import_observation_batch, publish_trusted_toolchain_successor, - semantic_or_toolchain_park, stage_import_discovery_successor, + close_import_discovery_successor, close_import_input_request, closed_discovery_continuation, + discovery_attempt, import_demand_frontier_for_roots, import_observation_ledger, + plan_delta_roots, publish_import_observation_batch, publish_trusted_toolchain_successor, + semantic_or_toolchain_park, stage_import_discovery_successor, stage_import_input_request, }; #[cfg(test)] use rue_compiler::unstable::{ @@ -807,12 +807,7 @@ fn drive_import_discovery_to_close( let _span = tracing::info_span!("import_stage").entered(); match &reclose { Some(reclose) => stage_import_discovery_successor(staging, reclose.delta), - None => staging.stage_import_discovery( - &snapshot, - context.clone(), - assembler.accepted_read_manifest().shared_slice(), - ledger.clone(), - ), + None => stage_import_input_request(staging, input_revision), } }; let plan = match staged { @@ -904,14 +899,12 @@ fn drive_import_discovery_to_close( .snapshot() .map_err(|error| SourceLoadError::Message(format!("Error: {error}")))?; debug_assert_eq!(final_plan.source_revision(), snapshot.source_revision()); - let ledger = import_observation_ledger(staging, input_revision) - .map_err(|error| SourceLoadError::Message(format!("Error: {error}")))?; // A trusted-toolchain successor closes over only the modules its opaque delta // capability authorizes, merging their topology into the committed // predecessor's closed graph; the initial close reduces the whole plan. let close_result = match &reclose { Some(reclose) => close_import_discovery_successor(staging, reclose.delta), - None => staging.close_import_discovery(ledger), + None => close_import_input_request(staging, input_revision), }; let closed = match close_result { Ok(closed) => closed, @@ -1895,9 +1888,16 @@ mod tests { }; assert_eq!(module_source_id(&result, "leaf.rue"), source_id); - assert!( - semantic_before.shares_owner(&semantic_after), - "an identical rewrite must retain the exact semantic terminal" + assert_eq!( + semantic_before + .function_views() + .map(|function| function.name().to_owned()) + .collect::>(), + semantic_after + .function_views() + .map(|function| function.name().to_owned()) + .collect::>(), + "an identical rewrite must preserve the canonical semantic projection" ); } diff --git a/crates/rue/src/timing.rs b/crates/rue/src/timing.rs index f0275d6ee..6dd17568f 100644 --- a/crates/rue/src/timing.rs +++ b/crates/rue/src/timing.rs @@ -1245,10 +1245,10 @@ mod tests { "missing compile -> compile_pipeline in batch edges: {compile_edges:?}" ); for edge in [ - ("compile_pipeline", "declaration_shells"), + ("semantic_astgen", "declaration_shells"), ("declaration_shells", "declaration_shell_prepare"), ("declaration_shell_prepare", "rir_declaration_index"), - ("compile_pipeline", "sema"), + ("semantic_astgen", "sema"), // Query-native codegen units publish their backend subphases // directly beneath the pipeline aggregate. They must not escape // to query-root timing spans or revive a peer codegen coordinator. diff --git a/docs/designs/0049-function-inlining.md b/docs/designs/0049-function-inlining.md index e3f18e9ee..ecba24782 100644 --- a/docs/designs/0049-function-inlining.md +++ b/docs/designs/0049-function-inlining.md @@ -170,6 +170,12 @@ inlining would simply make every caller *uncacheable*. §4 designs the fix. ### The semantic dependency manifest (ADR-0050) — what it is and is not +> **Historical architecture:** ADR-0063 superseded ADR-0050 and removed the +> whole-program manifest and `semantic_invalidation_plan`. An inlining +> implementation must express these dependencies through the canonical per-key +> body-reference/query graph. The discussion below explains the distinction +> that motivated this design; its named APIs and source locations are retired. + The dependency-derived invalidation machinery is **ADR-0050 (Stable semantic dependency manifests)**, with session support in `crates/rue-compiler/src/session.rs`: diff --git a/docs/designs/0050-semantic-dependency-manifest.md b/docs/designs/0050-semantic-dependency-manifest.md index 7a09d82bc..bef19c4e3 100644 --- a/docs/designs/0050-semantic-dependency-manifest.md +++ b/docs/designs/0050-semantic-dependency-manifest.md @@ -1,18 +1,27 @@ --- id: 0050 title: Stable semantic dependency manifests -status: accepted +status: superseded tags: [compiler, incremental, tooling] feature-flag: null created: 2026-07-12 accepted: 2026-07-13 implemented: 2026-07-14 spec-sections: [] -superseded-by: +superseded-by: 0063 --- # Stable semantic dependency manifests +## Status + +Superseded by [ADR-0063](0063-parallel-demand-driven-incremental-compilation.md) +on 2026-07-29. The stable-identity and fail-closed dependency principles remain +binding, but ADR-0063 replaces the whole-program manifest, invalidation planner, +and last-successful durable body/CFG caches with canonical per-key revisioned +queries. The design below records the retired architecture and is not a +description of the current `CompilerSession` API. + ## Decision `CompilerSession` represents reusable declaration, body, specialization, and CFG diff --git a/docs/designs/0051-canonical-import-resolution-authority.md b/docs/designs/0051-canonical-import-resolution-authority.md index e032e4c9c..7dc212a05 100644 --- a/docs/designs/0051-canonical-import-resolution-authority.md +++ b/docs/designs/0051-canonical-import-resolution-authority.md @@ -463,7 +463,7 @@ tooling. | `rue-air/src/types.rs::ModuleId` | **Keep as local** | Remains a compact semantic-epoch `u32` used by `Type::Module`/`ModuleDef`; never serves as durable compiler identity or a cache key across epochs. | | AIR `canonical_file_id` and module physical-path joins | **Change** | Join local AIR ID through the deterministic bridge to durable ID and current provenance; retain physical paths only as thin current-revision metadata adapters, not resolution. | | Declaration, comptime, type-check, member-access, and body import consumers | **Change** | All consume the same resolved records; none accepts path/env inputs or creates import diagnostics. | -| `semantic_dependency_inputs` / ADR-0050 module edges | **Keep and tighten** | Continue consuming `CanonicalImportGraph`; invalid/non-closed graphs fail closed and no import rediscovery occurs. | +| Revisioned semantic module-input edges | **Keep and tighten** | Consume `CanonicalImportGraph` through canonical per-key queries; invalid/non-closed graphs fail closed and no import rediscovery occurs. ADR-0063 retired ADR-0050's whole-program manifest projection. | | Missing/ambiguous/std diagnostic creation across CLI and AIR | **Remove/centralize** | One compiler diagnostic projection from outcomes/observations to every parser-owned site. | | Intrinsic wrong-arity/non-string diagnostics | **Keep behavior, change owner** | Parser-owned invalid-shape sites project the existing intrinsic diagnostics before sema and never initiate discovery. | | Declared source manifest, observation ledger, and accepted read manifest | **Keep distinct** | Permission, request/operation outcomes, accepted content reads, and semantic topology are separate revisioned values; denial is not a probe and a probe is not scope. | diff --git a/docs/designs/0061-supported-compiler-facade.md b/docs/designs/0061-supported-compiler-facade.md index ef7f70512..db32c36a9 100644 --- a/docs/designs/0061-supported-compiler-facade.md +++ b/docs/designs/0061-supported-compiler-facade.md @@ -250,8 +250,8 @@ of the supported API. | Move internal/direct owner | `CompilerSessionWork`, `FrontendQueryWork`, `FrontendRetentionMetrics`, `DifferentialOracleFault` | An explicitly unstable metrics/test-support owner provides owned snapshots for benchmarks and differential tests. No record grants access to query state. | | Remove | `FRONTEND_DIAGNOSTIC_RETENTION_LIMIT`, `FRONTEND_INVALIDATION_PLAN_RETENTION_LIMIT` | Retention policy is implementation detail; metrics may report observed capacity. | | Move internal | `DefinitionQueryRecord`, `SemanticQueryRecord`, `ImportDiagnosticInputDescriptor`, `ImportGraphInputDescriptor`, `ImportDiscoveryRevisionArtifact`, `ImportDiscoveryRevisionStatus` | Query keys, attempts, and revision publications. | -| Move internal | `SemanticDependencyBlocker`, `SemanticDependencyIncompleteReason`, `SemanticDependencyInputManifest`, `SemanticDependencyManifestWork`, `SemanticDependencySurface` | Durable dependency-manifest implementation under ADR-0050. A metrics view may summarize counts/reasons with unstable DTOs. | -| Move internal | `SemanticFullInvalidationReason`, `SemanticInvalidationPlan`, `SemanticInvalidationScope`, `SemanticInvalidationWork` | Invalidation selection and work are solely query-engine state. | +| Remove | `SemanticDependencyBlocker`, `SemanticDependencyIncompleteReason`, `SemanticDependencyInputManifest`, `SemanticDependencyManifestWork`, `SemanticDependencySurface` | ADR-0063 supersedes ADR-0050's whole-program dependency-manifest projection; canonical per-key queries own dependency evidence directly. | +| Remove | `SemanticFullInvalidationReason`, `SemanticInvalidationPlan`, `SemanticInvalidationScope`, `SemanticInvalidationWork` | ADR-0063 replaces the peer invalidation planner with revisioned per-key validation. | | Move internal | `StableBodyDependencyInputRecord`, `StableBuiltinTypeCallHeadInput`, `StableDeclarationTypeCallHeadDependency`, `StableDeclarationTypeDependency`, `StableDefinitionFingerprint`, `StableDefinitionFingerprintPrecision`, `StableDefinitionInputFingerprint`, `StableFreeFunctionDependency`, `StableModuleImportDependency`, `StableNamedConstDependency`, `StableNamedConstDependencyTarget`, `StableNamedDestructorDependency`, `StableNamedMethodDependency`, `StableNamedMethodDependencyTarget` | Cache authorization, fingerprints, and durable dependency edges. | | Move internal | `CodegenInputDescriptor`, `LinkInputDescriptor`, `SemanticInputDescriptor`, `ModuleResolutionInput`, `ModuleResolutionInputs`, `SourceStore`, `StableLinkerInput`, `StableOptLevel`, `StablePreviewFeatures` | Typed query keys and internal source storage. Public request values remain the authority at the boundary. | | Move internal | `DURABLE_ORDINARY_BODY_SCHEMA_VERSION`, `DURABLE_SPECIALIZED_BODY_SCHEMA_VERSION`, `DurableAirInst`, `DurableAirInstData`, `DurableAirRef`, `DurableBodyAnchor`, `DurableBodyConversionFailure`, `DurableBodyProjectionFailure`, `DurableBodyWork`, `DurableCallArg`, `DurableMatchArm`, `DurableOrdinaryBody`, `DurableOrdinaryBodyPayload`, `DurablePattern`, `DurablePlace`, `DurablePlaceRef`, `DurableProjection`, `DurableSpecializedBody`, `DurableSpecializedBodyPayload`, `convert_semantic_specialized_body_exports` | Body cache schemas, validation, projection, and conversion stay session-owned. Operational compatibility is reported as unstable status, never constructible payloads. | @@ -281,7 +281,7 @@ operations have these dispositions: | View-wrap | `latest_diagnostics`, `latest_successful_diagnostics`, `last_good_semantic_diagnostics`, `most_recent_diagnostics_for`, `diagnostics_for`, `import_diagnostics` | Return stable diagnostic views with explicit current/last-good provenance. | | Move internal/direct owner | `update_for_presentation`, `work` | Presentation selection and metrics move to explicit unstable requests/snapshots. | | Move internal/direct owner | `oracle_executable`, `inject_stale_query_for_oracle` | Crate-private test support preserves differential validation without making fault injection or oracle knobs a supported compiler API. | -| Move internal | `discovery_attempt`, `last_good_discovery`, `committed_import_discovery`, `stable_definitions`, `merge`, `semantic_dependency_inputs`, `semantic_invalidation_plan`, `executable_in_compile_scope` | Query attempts, definition binding, canonical merge, durable reuse decisions, invalidation, and scoped orchestration stay session-owned. Benchmarks may trigger them through record-free unstable adapters. | +| Move internal / remove | `discovery_attempt`, `last_good_discovery`, `committed_import_discovery`, `stable_definitions`, `merge`, `executable_in_compile_scope`; retired `semantic_dependency_inputs`, `semantic_invalidation_plan` | Query attempts, definition binding, canonical merge, and scoped orchestration stay session-owned. ADR-0063 removes the peer whole-program dependency/invalidation operations instead of exposing adapters for them. | `CompilerSessionUpdate` likewise keeps only success/diagnostic/view access needed to publish a source request. Raw parse work and invalidation accessors move to diff --git a/docs/designs/0063-parallel-demand-driven-incremental-compilation.md b/docs/designs/0063-parallel-demand-driven-incremental-compilation.md index ea89950fa..23d8f7d1d 100644 --- a/docs/designs/0063-parallel-demand-driven-incremental-compilation.md +++ b/docs/designs/0063-parallel-demand-driven-incremental-compilation.md @@ -9,10 +9,10 @@ accepted: 2026-07-18 implemented: spec-sections: [] superseded-by: -supersedes: [0045, 0053] +supersedes: [0045, 0050, 0053] amends: [0051] amended-by: [0066] -relates: ["ADR-0050", "ADR-0052", "ADR-0055", "ADR-0058", "ADR-0061", "RUE-328", "RUE-648", "RUE-812", "RUE-1021", "RUE-1022", "RUE-1023", "RUE-1024", "RUE-1025", "RUE-1026", "RUE-1027", "RUE-1028", "RUE-1029", "RUE-1030", "RUE-1031", "RUE-1032", "RUE-1033", "RUE-1137"] +relates: ["ADR-0052", "ADR-0055", "ADR-0058", "ADR-0061", "RUE-328", "RUE-648", "RUE-812", "RUE-1021", "RUE-1022", "RUE-1023", "RUE-1024", "RUE-1025", "RUE-1026", "RUE-1027", "RUE-1028", "RUE-1029", "RUE-1030", "RUE-1031", "RUE-1032", "RUE-1033", "RUE-1137", "RUE-1210"] --- # ADR-0063: Parallel demand-driven incremental compilation @@ -749,17 +749,17 @@ Tracked in Linear under the RUE-648 epic. The dependency order is: SCCs; move existing Rayon CFG/backend parallelism onto the shared budget; and prove progress plus one-worker/many-worker equivalence. — RUE-1028 (blocked by RUE-1027) -- [ ] **Phase 8: Type/layout/ABI/drop queries.** Replace full-pool scans and +- [x] **Phase 8: Type/layout/ABI/drop queries.** Replace full-pool scans and destructor roots with demand-driven facts, layouts, call ABI, and glue. — RUE-1029 (blocked by RUE-1028) -- [ ] **Phase 9: CFG and optimization queries.** Publish per-function +- [x] **Phase 9: CFG and optimization queries.** Publish per-function unoptimized and optimized CFG artifacts with precise layout, warning, string, symbol, and interprocedural dependencies. — RUE-1030 (blocked by RUE-1029) -- [ ] **Phase 10: MIR and `CodegenUnit` queries.** Query both native backends per +- [x] **Phase 10: MIR and `CodegenUnit` queries.** Query both native backends per reached function, normalize link atoms, migrate all backend presentation to the canonical path, and retain only justified backend boundaries. — RUE-1031 (blocked by RUE-1030) -- [ ] **Phase 11: `ProgramImagePlan` and fresh-link adapter.** Aggregate stable +- [x] **Phase 11: `ProgramImagePlan` and fresh-link adapter.** Aggregate stable typed units, project the current per-function objects, invoke the existing fresh internal/system linker paths, and establish the delta/fingerprint contract for follow-up direct and incremental internal linking. — RUE-1032 @@ -771,6 +771,13 @@ Tracked in Linear under the RUE-648 epic. The dependency order is: warm edit-to-codegen and edit-to-runnable baselines. — RUE-1033 (blocked by RUE-1032) + The selected-state compatibility layer and peer cache state are deleted, but + this phase remains open until the maintainers choose the initial retained-byte + and dependency-lease budgets for the representative-project gate. Existing + per-family terminal counts safely evict unprotected terminals and report + protected-root pressure, but they are not a cross-family byte policy and must + not be presented as one. RUE-1210 tracks that decision and enforcement work. + Each family migrates through the compatibility shim one at a time and must pass the cold-versus-reused differential oracle before the next family moves. Each phase is additive until its replacement is proven. The superseded whole-program diff --git a/docs/designs/README.md b/docs/designs/README.md index a2c71ccb3..90e1449a9 100644 --- a/docs/designs/README.md +++ b/docs/designs/README.md @@ -212,7 +212,7 @@ The table is generated from ADR frontmatter. Run | [0047](0047-root-module-build-inputs.md) | Root-module compilation units and build-system inputs | Accepted | modules, compiler, build-system, packages, cli, language-shape | | [0048](0048-shared-codegen-middle-layer.md) | Shared codegen middle layer (reduce x86-64/aarch64 backend duplication) | Accepted | codegen, architecture, backends, refactor, maintainability | | [0049](0049-function-inlining.md) | Function Inlining | Accepted | compiler, codegen, optimization | -| [0050](0050-semantic-dependency-manifest.md) | Stable semantic dependency manifests | Accepted | compiler, incremental, tooling | +| [0050](0050-semantic-dependency-manifest.md) | Stable semantic dependency manifests | Superseded | compiler, incremental, tooling | | [0051](0051-canonical-import-resolution-authority.md) | CanonicalImportGraph as the sole import-resolution authority | Accepted | architecture, compiler, modules, incremental, tooling | | [0052](0052-canonical-physical-type-layout.md) | Canonical Physical Type Layout | Accepted | types, semantics, compiler, codegen, abi, memory | | [0053](0053-typed-compiler-query-state.md) | Typed CompilerSession query state | Superseded | architecture, compiler, incremental, tooling | diff --git a/scripts/validate-debug-assert-policy.py b/scripts/validate-debug-assert-policy.py index 53834f70a..27892196d 100755 --- a/scripts/validate-debug-assert-policy.py +++ b/scripts/validate-debug-assert-policy.py @@ -48,15 +48,13 @@ class Allowance(NamedTuple): "crates/rue-compiler/src/definition_snapshot.rs": Allowance(1, "redundant definition arena identity check"), "crates/rue-compiler/src/diagnostic_attempt_store.rs": Allowance(2, "redundant diagnostic retention accounting"), "crates/rue-compiler/src/parsed_modules.rs": Allowance(1, "redundant source ownership check"), - "crates/rue-compiler/src/query_graph.rs": Allowance(3, "redundant query-node lifecycle checks"), "crates/rue-compiler/src/revisioned_query_database.rs": Allowance( 2, "redundant memo-retention and canonical-root accounting" ), "crates/rue-compiler/src/semantic_query_nucleus.rs": Allowance(1, "redundant semantic query category check"), - "crates/rue-compiler/src/session.rs": Allowance(7, "redundant canonical-session phase and provenance checks"), + "crates/rue-compiler/src/session.rs": Allowance(6, "redundant canonical-session phase and provenance checks"), "crates/rue-compiler/src/source_identity.rs": Allowance(4, "redundant normalized-path representation checks"), "crates/rue-compiler/src/source_snapshot.rs": Allowance(1, "redundant source arena identity check"), - "crates/rue-compiler/src/typed_query_store.rs": Allowance(1, "redundant typed-query key check"), "crates/rue-error/src/lib.rs": Allowance(1, "redundant diagnostic rendering bounds check"), "crates/rue-oracle/src/lib.rs": Allowance(2, "oracle harness bookkeeping checks, not compiler correctness gates"), "crates/rue-parser/src/parser/shared.rs": Allowance(2, "redundant parser entry preconditions"),