diff --git a/src/v1/00_core.dag b/src/v1/00_core.dag index 65988c59820..4567c553c1a 100644 --- a/src/v1/00_core.dag +++ b/src/v1/00_core.dag @@ -158,6 +158,17 @@ type TextFile { content: String } +// UnlistedVariantValueUse EXISTS SEPARATELY FROM UnlistedImportUse ON PURPOSE. The situation is the +// same one UnlistedImportUse reports for a TYPE -- a name used without an import -- and reusing that +// constructor for the bare nullary VARIANT case was tried and withdrawn: v1.compiler.emit_rust +// group_unlisted_type_names folds every diagnostic in typed.diagnostics selecting UnlistedImportUse by +// constructor alone, and hands the result to reference_derived_use_lines, which SYNTHESIZES use-lines +// from it. That selector is total over the diagnostic type and blind to the stage that minted it -- an +// ErrorNode carries a module name and a diagnostic and nothing that says which stage produced it -- so +// any stage emitting UnlistedImportUse is silently wired into production emission, which no author +// would see from the code they were editing. A distinct constructor is therefore not a naming +// preference: it is what makes the standing prohibition on adding bare variants to synthesis output +// hold BY CONSTRUCTION rather than by a byte-diff that can only report 'not on this subject today'. type CompilerDiagnostic = UnresolvedImport { module_path: String, importing_module: String, span: SourceSpan } | MissingExport { name: String, module_path: String, importing_module: String, span: SourceSpan } @@ -199,6 +210,7 @@ type CompilerDiagnostic | DeclaredTypeNotInhabited { position: String, expected: String, got: String, span: SourceSpan } | DeclaredTypeInhabitanceUndecided { position: String, reason: String, span: SourceSpan } | UnlistedImportUse { name: String, span: SourceSpan } + | UnlistedVariantValueUse { name: String, span: SourceSpan } | AmbiguousReference { name: String, candidates: List, span: SourceSpan } | DataReferenceVisibilityBudgetExceeded { name: String, span: SourceSpan } | ParameterDefaultFormNotAdmitted { parameter: String, admitted: List, span: SourceSpan } @@ -350,6 +362,7 @@ fn diagnostic_to_span(d: CompilerDiagnostic) -> SourceSpan { DeclaredTypeNotInhabited { position: _, expected: _, got: _, span: s } => s DeclaredTypeInhabitanceUndecided { position: _, reason: _, span: s } => s UnlistedImportUse { name: _, span: s } => s + UnlistedVariantValueUse { name: _, span: s } => s AmbiguousReference { name: _, candidates: _, span: s } => s DataReferenceVisibilityBudgetExceeded { name: _, span: s } => s ParameterDefaultFormNotAdmitted { parameter: _, admitted: _, span: s } => s @@ -432,6 +445,7 @@ fn diagnostic_to_message(d: CompilerDiagnostic) -> String { DeclaredTypeInhabitanceUndecided { position: pos, reason: r, span: _ } => concat("declared-type inhabitance is undecidable at the ", pos, " (", r, "): the modeled facts do not settle whether the produced value inhabits its declared type, so no verdict is asserted in either direction") UnlistedImportUse { name: n, span: _ } => concat("unlisted import use '", n, "' (referenced but not in any import's name list)") + UnlistedVariantValueUse { name: n, span: _ } => concat("unlisted variant value use '", n, "' (a coproduct arm referenced but not in any import's name list)") AmbiguousReference { name: n, candidates: cs, span: _ } => concat("ambiguous reference '", n, "': ", to_string(value: cs |> count), " candidates: ", join(cs, separator: ", "), " — qualify by containment path, alias, or rename") DataReferenceVisibilityBudgetExceeded { name: n, span: _ } => concat("visible declarations of '", n, "' could not be enumerated: the import re-export walk exceeded its depth bound, so no verdict is asserted about how many declarations answer to the name") @@ -490,6 +504,7 @@ data diagnostic_frontier_occurrence_key_note: String = "The canonical accessor f fn is_error_diagnostic(d: CompilerDiagnostic) -> Bool { match d { UnlistedImportUse { name: _, span: _ } => false + UnlistedVariantValueUse { name: _, span: _ } => false MethodExistenceFrontierAdmitted { method: _, receiver_type: _, trigger: _, span: _ } => false ReceiverTypeUnestablished { method: _, span: _ } => false WhereRefinementUnenforced { predicate: _, formal_type: _, reason: r, span: _ } => @@ -506,6 +521,7 @@ fn is_interpreter_blocking_diagnostic(d: CompilerDiagnostic) -> Bool { WhereRefinementUnenforced { predicate: _, formal_type: _, reason: r, span: _ } => !is_where_refinement_unenforced_advisory_reason(reason: r) UnlistedImportUse { name: _, span: _ } => false + UnlistedVariantValueUse { name: _, span: _ } => false MethodExistenceFrontierAdmitted { method: _, receiver_type: _, trigger: _, span: _ } => false ReceiverTypeUnestablished { method: _, span: _ } => false ServiceConfigReferenceJudgmentDeferred { field: _, referenced_name: _, trigger: _, span: _ } => false @@ -517,6 +533,7 @@ fn is_interpreter_blocking_diagnostic(d: CompilerDiagnostic) -> Bool { fn is_discovery_corpus_advisory_typecheck_diagnostic(d: CompilerDiagnostic) -> Bool { match d { UnlistedImportUse { name: _, span: _ } => true + UnlistedVariantValueUse { name: _, span: _ } => true MethodExistenceFrontierAdmitted { method: _, receiver_type: _, trigger: _, span: _ } => true ReceiverTypeUnestablished { method: _, span: _ } => true ServiceConfigReferenceJudgmentDeferred { field: _, referenced_name: _, trigger: _, span: _ } => true diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index b85f378c3d9..6b9130b4507 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -37,7 +37,7 @@ import v1.std.core { Cardinality, Required, CardOptional, ErrorNode, make_error_node, is_error_diagnostic, diagnostic_to_span, FrontierOccurrenceKey, diagnostic_frontier_occurrence_key, - InternalError, TypeMismatch, VariantCollision, SoleConstructorViolation, OptionalCastNotEliminated, BareNoneNotAdmittedByFieldType, ConstructorCallAdmissionRefused, UnresolvedType, FieldNotFound, MethodNotFound, MethodExistenceUndecided, MethodExistenceFrontierAdmitted, ReceiverTypeUnestablished, FrontierOccurrenceBudgetExceeded, MissingField, AmbiguousReference, ServiceConfigReferenceJudgmentDeferred, + InternalError, TypeMismatch, VariantCollision, SoleConstructorViolation, OptionalCastNotEliminated, BareNoneNotAdmittedByFieldType, ConstructorCallAdmissionRefused, UnresolvedType, FieldNotFound, MethodNotFound, MethodExistenceUndecided, MethodExistenceFrontierAdmitted, ReceiverTypeUnestablished, FrontierOccurrenceBudgetExceeded, MissingField, AmbiguousReference, ServiceConfigReferenceJudgmentDeferred, UnlistedVariantValueUse, CallArgumentNameUnknown, CallPositionalSurplus, CallPositionalDeficit, CallNamedArgOnFunctionValue, CallArgumentDuplicate, resource_use_name_at, resource_use_resource, is_child_accessor_in_model, is_tree_size_reducing, is_property_contraction, @@ -660,6 +660,49 @@ fn variant_owner_node(scope: InferScope, name: String) -> Node? { } } +// THE BARE NULLARY VARIANT REFERENCE'S OWN UNLISTED-USE OBSERVATION, ON ITS OWN CONSTRUCTOR, and why +// it is emitted here +// rather than left to the type path. `resolve_node_bounded`'s masked-leaf check reports +// UnlistedImportUse for a TYPE named without an import; the identical situation one channel over -- +// a coproduct ARM named without an import -- reported nothing at all, because the arm binds through +// the constructor channel (`merge_global_bare_variant_locals` merges a whole-closure base under the +// checked own+direct-imports fold) and never reaches that leaf. Measured on a discriminating pair in +// `gunbc.constructor_binding_channel`: same fixture, same absent import, advisory for the type +// reference and silence for the arm. +// +// WHY THAT SILENCE IS NOT COSMETIC. `v1.compiler.emit_rust` `collect_value_ref_names` harvests +// ExprCall unconditionally and ExprRecordLit through `record_lit_ref_names`, so the payload-carrying +// constructor forms already reach the reference-derived use-line producer. ExprVar carrying a +// VariantValueBinding falls to that function's `_ => []`, so the BARE NULLARY arm -- Absent, Empty, +// NoConnective, the unit arm of nearly every coproduct here -- contributes nothing, and a census of +// the use-line repair population built on the existing instrument cannot be complete while it does. +// This adds the missing observation; it does not change what binds or what refuses. +// +// AND IT DOES NOT REACH THE EMITTER, BY CONSTRUCTION RATHER THAN BY MEASUREMENT. The first cut of this +// observation reused `UnlistedImportUse`, which `v1.compiler.emit_rust` `group_unlisted_type_names` +// folds out of `typed.diagnostics` -- a set that carries `infer_diags` -- into the input +// `reference_derived_use_lines` synthesizes use-lines from. That would have added bare variants to +// production synthesis output, which a standing hold forbids until the pre-cut population is counted. +// `UnlistedVariantValueUse` is a separate constructor that selector cannot match, so byte-identical +// emission is a property of the shape rather than a diff someone has to keep re-running. The reason +// the constructor exists is recorded on the constructor, in v1.std.core. +// +// THE OWNER-FILE TEST IS WHAT KEEPS IT FROM FIRING ON LOCAL DECLARATIONS. `source_visible_names` +// carries imports and the kernel surface, never the module's own declarations, so membership alone +// would report every locally-declared arm. The owner node's file is compared with the reference's, +// which is the same identity test `insert_variant_owner_checked` already uses for same-owner. +fn unlisted_variant_use_diagnostics(scope: InferScope, name: String, span: SourceSpan, owner: Node?) -> List { + match owner { + Present { value: owner_node } => + if owner_node.span.file != span.file + && map_is_empty(m: scope.type_env.source_visible_names) == false + && map_has(m: scope.type_env.source_visible_names, key: name) == false { + [make_error_node(diagnostic: UnlistedVariantValueUse { name: name, span: span }, module_name: scope.module_name)] + } else { [] } + Absent => [] + } +} + fn infer_var_binding_kind(scope: InferScope, name: String) -> VarBindingKind { match lookup_variant_parent_enum(scope: scope, name: name) { Present { value: parent_enum } => VariantValueBinding { parent_enum: parent_enum } @@ -4263,14 +4306,17 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu let scope_parent = lookup_variant_parent_enum(scope: scope, name: name) match scope_parent { Present { value: scope_enum } => - ok_infer(texpr: make_named_expr_node( - name: name, - expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: scope_enum } } }, - children: [], - inferred: Present { value: Resolved { node: variant_reference_inferred_node(expected: expected, name: name, owner_name: scope_enum, scope: scope, fallback: binding.resolved) } }, - span: span, - name_span: span - )) + InferResult { + typed: make_named_expr_node( + name: name, + expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: scope_enum } } }, + children: [], + inferred: Present { value: Resolved { node: variant_reference_inferred_node(expected: expected, name: name, owner_name: scope_enum, scope: scope, fallback: binding.resolved) } }, + span: span, + name_span: span + ), + diagnostics: unlisted_variant_use_diagnostics(scope: scope, name: name, span: span, owner: variant_owner_node(scope: scope, name: name)) + } Absent => let binding_kind = infer_var_binding_kind(scope: scope, name: name) ok_infer(texpr: make_named_expr_node( @@ -4313,14 +4359,17 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu let scope_parent = lookup_variant_parent_enum(scope: scope, name: name) match scope_parent { Present { value: scope_enum } => - ok_infer(texpr: make_named_expr_node( - name: name, - expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: scope_enum } } }, - children: [], - inferred: Present { value: Resolved { node: variant_reference_inferred_node(expected: expected, name: name, owner_name: scope_enum, scope: scope, fallback: gbinding.resolved) } }, - span: span, - name_span: span - )) + InferResult { + typed: make_named_expr_node( + name: name, + expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: scope_enum } } }, + children: [], + inferred: Present { value: Resolved { node: variant_reference_inferred_node(expected: expected, name: name, owner_name: scope_enum, scope: scope, fallback: gbinding.resolved) } }, + span: span, + name_span: span + ), + diagnostics: unlisted_variant_use_diagnostics(scope: scope, name: name, span: span, owner: variant_owner_node(scope: scope, name: name)) + } Absent => let binding_kind = infer_var_binding_kind(scope: scope, name: name) ok_infer(texpr: make_named_expr_node( @@ -4336,14 +4385,17 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu let expected_variant_enum = expected_variant_owner_instantiation(expected: expected, name: name, scope: scope) match expected_variant_enum { Present { value: exp_enum } => - ok_infer(texpr: make_named_expr_node( - name: name, - expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: authored_name_at(source_indices: scope.type_env.source_indices, node: exp_enum) } } }, - children: [], - inferred: Present { value: Resolved { node: exp_enum } }, - span: span, - name_span: span - )) + InferResult { + typed: make_named_expr_node( + name: name, + expr_data: ExprVar { binding_kind: Present { value: VariantValueBinding { parent_enum: authored_name_at(source_indices: scope.type_env.source_indices, node: exp_enum) } } }, + children: [], + inferred: Present { value: Resolved { node: exp_enum } }, + span: span, + name_span: span + ), + diagnostics: unlisted_variant_use_diagnostics(scope: scope, name: name, span: span, owner: Present { value: exp_enum }) + } Absent => let var_ambiguity_cands = global_bare_strict_ambiguity_candidates(env: scope.type_env, name: name) if (var_ambiguity_cands |> count) > 0 { diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 88509916883..341f4f17a75 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -3746,6 +3746,7 @@ pub fn compile_clean_diagnostic_is_advisory(d: &Rc) -> bool { && matches!( d.diagnostic.as_ref(), crate::v1_std_core::CompilerDiagnostic::UnlistedImportUse { .. } + | crate::v1_std_core::CompilerDiagnostic::UnlistedVariantValueUse { .. } | crate::v1_std_core::CompilerDiagnostic::ComplexityUnknown { .. } | crate::v1_std_core::CompilerDiagnostic::WhereRefinementUnenforced { .. } // A non-blocking variant that is absent from this list is counted by @@ -5416,6 +5417,7 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::ServiceConfigReferenceJudgmentDeferred { .. } => { "ServiceConfigReferenceJudgmentDeferred" } + CompilerDiagnostic::UnlistedVariantValueUse { .. } => "UnlistedVariantValueUse", }; let name = match d.diagnostic.as_ref() { CompilerDiagnostic::UnresolvedImport { module_path, .. } => module_path.clone(), @@ -5458,6 +5460,7 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::DeclaredTypeNotInhabited { position, .. } => position.clone(), CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { position, .. } => position.clone(), CompilerDiagnostic::UnlistedImportUse { name, .. } => name.clone(), + CompilerDiagnostic::UnlistedVariantValueUse { name, .. } => name.clone(), CompilerDiagnostic::AmbiguousReference { name, .. } => name.clone(), CompilerDiagnostic::DataReferenceVisibilityBudgetExceeded { name, .. } => name.clone(), CompilerDiagnostic::ParameterDefaultFormNotAdmitted { parameter, .. } => parameter.clone(), diff --git a/src/v1/stage0/src/emitted_population.rs b/src/v1/stage0/src/emitted_population.rs index 60c24705826..3339c522d1e 100644 --- a/src/v1/stage0/src/emitted_population.rs +++ b/src/v1/stage0/src/emitted_population.rs @@ -131,6 +131,7 @@ // src/v1_rt.rs // src/v1_std_core.rs // src/v1_test_non_ascii_perf_fixture.rs +// src/v1_tests_claim_bare_variant_reference_occurrence_control_test.rs // src/v1_tests_claim_checkpoint_identity_keying_witness_test.rs // src/v1_tests_claim_transport_roster_closure_witness_test.rs // src/wt_a.rs diff --git a/src/v1/stage0/src/lib.rs b/src/v1/stage0/src/lib.rs index c80ea6c571e..dad66185ebb 100644 --- a/src/v1/stage0/src/lib.rs +++ b/src/v1/stage0/src/lib.rs @@ -143,6 +143,7 @@ pub mod v1_probe_emit_interp; pub mod v1_rt; pub mod v1_std_core; pub mod v1_test_non_ascii_perf_fixture; +pub mod v1_tests_claim_bare_variant_reference_occurrence_control_test; pub mod v1_tests_claim_checkpoint_identity_keying_witness_test; pub mod v1_tests_claim_transport_roster_closure_witness_test; pub mod wt_a; diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 166c3e06a62..29488f037a0 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -190,7 +190,7 @@ use crate::v1_std_core::CompilerDiagnostic::{ FrontierOccurrenceBudgetExceeded, InternalError, MethodExistenceFrontierAdmitted, MethodExistenceUndecided, MethodNotFound, MissingField, OptionalCastNotEliminated, ReceiverTypeUnestablished, ServiceConfigReferenceJudgmentDeferred, SoleConstructorViolation, - TypeMismatch, UnresolvedType, VariantCollision, + TypeMismatch, UnlistedVariantValueUse, UnresolvedType, VariantCollision, }; use crate::v1_std_core::Connective::{Arrow, Conj, Disj, NoConnective}; use crate::v1_std_core::ExprData::{ @@ -1044,6 +1044,37 @@ pub fn variant_owner_node(scope: Rc, name: String) -> Option, + name: String, + span: Rc, + owner: Option>, +) -> Rc>> { + match owner.clone() { + Some(owner_node) => { + if (((owner_node.span.clone().file.clone() != span.file.clone()) + && (v1_rt::map_is_empty(&scope.type_env.clone().source_visible_names.clone()) + == false)) + && (v1_rt::map_has( + &scope.type_env.clone().source_visible_names.clone(), + name.clone(), + ) == false)) + { + Rc::new(vec![make_error_node( + Rc::new(CompilerDiagnostic::UnlistedVariantValueUse { + name: name.clone(), + span: span.clone(), + }), + scope.module_name.clone(), + )]) + } else { + Rc::new(vec![]) + } + } + None => Rc::new(vec![]), + } +} + pub fn infer_var_binding_kind(scope: Rc, name: String) -> Rc { match lookup_variant_parent_enum(scope.clone(), name.clone()) { Some(parent_enum) => Rc::new(VarBindingKind::VariantValueBinding { @@ -6951,230 +6982,188 @@ pub fn infer_expr_body( scope.clone(), ) { Some(refusal) => refusal.clone(), - None => match v1_rt::map_get(&scope.locals.clone(), name.clone()) { - Some(binding) => { - let scope_parent = lookup_variant_parent_enum(scope.clone(), name.clone()); - match scope_parent.clone() { - Some(scope_enum) => ok_infer(make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: Some(Rc::new( - VarBindingKind::VariantValueBinding { - parent_enum: scope_enum.clone(), - }, - )), - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: variant_reference_inferred_node( - expected.clone(), + None => { + match v1_rt::map_get(&scope.locals.clone(), name.clone()) { + Some(binding) => { + let scope_parent = + lookup_variant_parent_enum(scope.clone(), name.clone()); + match scope_parent.clone() { + Some(scope_enum) => Rc::new(InferResult { + typed: make_named_expr_node( name.clone(), - scope_enum.clone(), + Rc::new(ExprData::ExprVar { + binding_kind: Some(Rc::new( + VarBindingKind::VariantValueBinding { + parent_enum: scope_enum.clone(), + }, + )), + }), + Rc::new(vec![]), + Some(Rc::new(InferredNode::Resolved { + node: variant_reference_inferred_node( + expected.clone(), + name.clone(), + scope_enum.clone(), + scope.clone(), + binding.resolved.clone(), + ), + })), + span.clone(), + span.clone(), + ), + diagnostics: unlisted_variant_use_diagnostics( scope.clone(), - binding.resolved.clone(), + name.clone(), + span.clone(), + variant_owner_node(scope.clone(), name.clone()), ), - })), - span.clone(), - span.clone(), - )), - None => { - let binding_kind = - infer_var_binding_kind(scope.clone(), name.clone()); - ok_infer(make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: Some(binding_kind.clone()), - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: binding.resolved.clone(), - })), - span.clone(), - span.clone(), - )) - } - } - } - None => match (*lookup_func_sig( - scope.func_env.clone(), - scope.type_env.clone(), - name.clone(), - )) - .clone() - { - FuncSigLookup::FuncSigResolved { sig: fsig, .. } => { - match ((fsig.params.clone().len() as i64) == 0) { - true => ok_infer(make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprCall { - call_semantics: Some(CallSemantics::PlainCallSemantics), - descent_evidence: None, - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: fsig.inferred.clone(), - })), - span.clone(), - span.clone(), - )), - false => ok_infer(make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: Some(Rc::new( - VarBindingKind::FunctionValueBinding, - )), - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: resolved_callable_type( - fsig.params.clone(), - fsig.inferred.clone(), - ), - })), - span.clone(), - span.clone(), - )), + }), + None => { + let binding_kind = + infer_var_binding_kind(scope.clone(), name.clone()); + ok_infer(make_named_expr_node( + name.clone(), + Rc::new(ExprData::ExprVar { + binding_kind: Some(binding_kind.clone()), + }), + Rc::new(vec![]), + Some(Rc::new(InferredNode::Resolved { + node: binding.resolved.clone(), + })), + span.clone(), + span.clone(), + )) + } } } - FuncSigLookup::FuncSigAmbiguous { - candidates: ambiguous_fn_candidates, - .. - } => ambiguous_reference_refusal( - name.clone(), - callable_candidate_labels(ambiguous_fn_candidates.clone()), - span.clone(), - scope.clone(), - ), - FuncSigLookup::FuncSigUnresolved => { - match lookup_binding_by_name(scope.type_env.clone(), name.clone()) { - Some(gbinding) => { - let scope_parent = - lookup_variant_parent_enum(scope.clone(), name.clone()); - match scope_parent.clone() { - Some(scope_enum) => ok_infer(make_named_expr_node( + None => { + match (*lookup_func_sig( + scope.func_env.clone(), + scope.type_env.clone(), + name.clone(), + )) + .clone() + { + FuncSigLookup::FuncSigResolved { sig: fsig, .. } => { + match ((fsig.params.clone().len() as i64) == 0) { + true => ok_infer(make_named_expr_node( name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: Some(Rc::new( - VarBindingKind::VariantValueBinding { - parent_enum: scope_enum.clone(), - }, - )), + Rc::new(ExprData::ExprCall { + call_semantics: Some( + CallSemantics::PlainCallSemantics, + ), + descent_evidence: None, }), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { - node: variant_reference_inferred_node( - expected.clone(), - name.clone(), - scope_enum.clone(), - scope.clone(), - gbinding.resolved.clone(), - ), + node: fsig.inferred.clone(), })), span.clone(), span.clone(), )), - None => { - let binding_kind = - infer_var_binding_kind(scope.clone(), name.clone()); - ok_infer(make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: Some(binding_kind.clone()), - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: gbinding.resolved.clone(), - })), - span.clone(), - span.clone(), - )) - } - } - } - None => { - let expected_variant_enum = - expected_variant_owner_instantiation( - expected.clone(), - name.clone(), - scope.clone(), - ); - match expected_variant_enum.clone() { - Some(exp_enum) => ok_infer(make_named_expr_node( + false => ok_infer(make_named_expr_node( name.clone(), Rc::new(ExprData::ExprVar { binding_kind: Some(Rc::new( - VarBindingKind::VariantValueBinding { - parent_enum: authored_name_at( - scope - .type_env - .clone() - .source_indices - .clone(), - exp_enum.clone(), - ), - }, + VarBindingKind::FunctionValueBinding, )), }), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { - node: exp_enum.clone(), + node: resolved_callable_type( + fsig.params.clone(), + fsig.inferred.clone(), + ), })), span.clone(), span.clone(), )), + } + } + FuncSigLookup::FuncSigAmbiguous { + candidates: ambiguous_fn_candidates, + .. + } => ambiguous_reference_refusal( + name.clone(), + callable_candidate_labels(ambiguous_fn_candidates.clone()), + span.clone(), + scope.clone(), + ), + FuncSigLookup::FuncSigUnresolved => { + match lookup_binding_by_name( + scope.type_env.clone(), + name.clone(), + ) { + Some(gbinding) => { + let scope_parent = lookup_variant_parent_enum( + scope.clone(), + name.clone(), + ); + match scope_parent.clone() { + Some(scope_enum) => Rc::new(InferResult { + typed: make_named_expr_node(name.clone(), Rc::new(ExprData::ExprVar { + binding_kind: Some(Rc::new(VarBindingKind::VariantValueBinding { + parent_enum: scope_enum.clone(), +})), +}), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { + node: variant_reference_inferred_node(expected.clone(), name.clone(), scope_enum.clone(), scope.clone(), gbinding.resolved.clone()), +})), span.clone(), span.clone()), + diagnostics: unlisted_variant_use_diagnostics(scope.clone(), name.clone(), span.clone(), variant_owner_node(scope.clone(), name.clone())), +}), + None => { + let binding_kind = infer_var_binding_kind(scope.clone(), name.clone()); +ok_infer(make_named_expr_node(name.clone(), Rc::new(ExprData::ExprVar { + binding_kind: Some(binding_kind.clone()), +}), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { + node: gbinding.resolved.clone(), +})), span.clone(), span.clone())) +}, +} + } None => { - let var_ambiguity_cands = - global_bare_strict_ambiguity_candidates( - scope.type_env.clone(), - name.clone(), - ); - if ((var_ambiguity_cands.clone().len() as i64) > 0) { - ambiguous_reference_refusal( + let expected_variant_enum = + expected_variant_owner_instantiation( + expected.clone(), name.clone(), - var_ambiguity_cands.clone(), - span.clone(), scope.clone(), - ) - } else { - { - let err_texpr = make_named_expr_node( - name.clone(), - Rc::new(ExprData::ExprVar { - binding_kind: None, - }), - Rc::new(vec![]), - Some(Rc::new(InferredNode::Resolved { - node: error_type(), - })), - span.clone(), - span.clone(), - ); - Rc::new(InferResult { - typed: err_texpr.clone(), - diagnostics: Rc::new(vec![ - inference_error( - v1_rt::concat( - v1_rt::concat( - "undefined variable '" - .to_string(), - name.clone(), - ), - "'".to_string(), - ), - span.clone(), - scope.module_name.clone(), - ), - ]), - }) - } - } + ); + match expected_variant_enum.clone() { + Some(exp_enum) => Rc::new(InferResult { + typed: make_named_expr_node(name.clone(), Rc::new(ExprData::ExprVar { + binding_kind: Some(Rc::new(VarBindingKind::VariantValueBinding { + parent_enum: authored_name_at(scope.type_env.clone().source_indices.clone(), exp_enum.clone()), +})), +}), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { + node: exp_enum.clone(), +})), span.clone(), span.clone()), + diagnostics: unlisted_variant_use_diagnostics(scope.clone(), name.clone(), span.clone(), Some(exp_enum.clone())), +}), + None => { + let var_ambiguity_cands = global_bare_strict_ambiguity_candidates(scope.type_env.clone(), name.clone()); +if ((var_ambiguity_cands.clone().len() as i64) > 0) { + ambiguous_reference_refusal(name.clone(), var_ambiguity_cands.clone(), span.clone(), scope.clone()) + } else { + { + let err_texpr = make_named_expr_node(name.clone(), Rc::new(ExprData::ExprVar { + binding_kind: None, +}), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { + node: error_type(), +})), span.clone(), span.clone()); +Rc::new(InferResult { + typed: err_texpr.clone(), + diagnostics: Rc::new(vec![inference_error(v1_rt::concat(v1_rt::concat("undefined variable '".to_string(), name.clone()), "'".to_string()), span.clone(), scope.module_name.clone())]), +}) +} + } +}, +} } } } } } - }, - }, + } + } } } ExprData::ExprFieldAccess { summary: _, .. } => { diff --git a/src/v1/stage0/src/v1_std_core.rs b/src/v1/stage0/src/v1_std_core.rs index 17620c44282..fe3fa8b47ef 100644 --- a/src/v1/stage0/src/v1_std_core.rs +++ b/src/v1/stage0/src/v1_std_core.rs @@ -549,6 +549,10 @@ pub enum CompilerDiagnostic { name: String, span: Rc, }, + UnlistedVariantValueUse { + name: String, + span: Rc, + }, AmbiguousReference { name: String, candidates: Rc>, @@ -719,6 +723,7 @@ pub fn diagnostic_to_span(d: Rc) -> Rc { CompilerDiagnostic::DeclaredTypeNotInhabited { span: s, .. } => s.clone(), CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { span: s, .. } => s.clone(), CompilerDiagnostic::UnlistedImportUse { span: s, .. } => s.clone(), + CompilerDiagnostic::UnlistedVariantValueUse { span: s, .. } => s.clone(), CompilerDiagnostic::AmbiguousReference { span: s, .. } => s.clone(), CompilerDiagnostic::DataReferenceVisibilityBudgetExceeded { span: s, .. } => s.clone(), CompilerDiagnostic::ParameterDefaultFormNotAdmitted { span: s, .. } => s.clone(), @@ -775,6 +780,7 @@ pub fn diagnostic_to_message(d: Rc) -> String { CompilerDiagnostic::DeclaredTypeNotInhabited { position: pos, expected: e, got: g, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("value does not inhabit its declared type at the ".to_string(), pos.clone()), ": declared '".to_string()), e.clone()), "', produced '".to_string()), g.clone()), "'".to_string()), CompilerDiagnostic::DeclaredTypeInhabitanceUndecided { position: pos, reason: r, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("declared-type inhabitance is undecidable at the ".to_string(), pos.clone()), " (".to_string()), r.clone()), "): the modeled facts do not settle whether the produced value inhabits its declared type, so no verdict is asserted in either direction".to_string()), CompilerDiagnostic::UnlistedImportUse { name: n, .. } => v1_rt::concat(v1_rt::concat("unlisted import use '".to_string(), n.clone()), "' (referenced but not in any import's name list)".to_string()), + CompilerDiagnostic::UnlistedVariantValueUse { name: n, .. } => v1_rt::concat(v1_rt::concat("unlisted variant value use '".to_string(), n.clone()), "' (a coproduct arm referenced but not in any import's name list)".to_string()), CompilerDiagnostic::AmbiguousReference { name: n, candidates: cs, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("ambiguous reference '".to_string(), n.clone()), "': ".to_string()), ((cs.clone().len() as i64)).to_string()), " candidates: ".to_string()), cs.clone().join(&", ".to_string())), " — qualify by containment path, alias, or rename".to_string()), CompilerDiagnostic::DataReferenceVisibilityBudgetExceeded { name: n, .. } => v1_rt::concat(v1_rt::concat("visible declarations of '".to_string(), n.clone()), "' could not be enumerated: the import re-export walk exceeded its depth bound, so no verdict is asserted about how many declarations answer to the name".to_string()), CompilerDiagnostic::ParameterDefaultFormNotAdmitted { parameter: p, admitted: forms, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("default value for parameter '".to_string(), p.clone()), "' is not an admitted form (admitted: ".to_string()), forms.clone().join(&", ".to_string())), ")".to_string()), @@ -849,6 +855,7 @@ pub fn diagnostic_frontier_occurrence_key_note() -> String { pub fn is_error_diagnostic(d: Rc) -> bool { match (*d.clone()).clone() { CompilerDiagnostic::UnlistedImportUse { .. } => false, + CompilerDiagnostic::UnlistedVariantValueUse { .. } => false, CompilerDiagnostic::MethodExistenceFrontierAdmitted { .. } => false, CompilerDiagnostic::ReceiverTypeUnestablished { .. } => false, CompilerDiagnostic::WhereRefinementUnenforced { reason: r, .. } => { @@ -874,6 +881,7 @@ pub fn is_interpreter_blocking_diagnostic(d: Rc) -> bool { !is_where_refinement_unenforced_advisory_reason(r.clone()) } CompilerDiagnostic::UnlistedImportUse { .. } => false, + CompilerDiagnostic::UnlistedVariantValueUse { .. } => false, CompilerDiagnostic::MethodExistenceFrontierAdmitted { .. } => false, CompilerDiagnostic::ReceiverTypeUnestablished { .. } => false, CompilerDiagnostic::ServiceConfigReferenceJudgmentDeferred { .. } => false, @@ -885,6 +893,7 @@ pub fn is_interpreter_blocking_diagnostic(d: Rc) -> bool { pub fn is_discovery_corpus_advisory_typecheck_diagnostic(d: Rc) -> bool { match (*d.clone()).clone() { CompilerDiagnostic::UnlistedImportUse { .. } => true, + CompilerDiagnostic::UnlistedVariantValueUse { .. } => true, CompilerDiagnostic::MethodExistenceFrontierAdmitted { .. } => true, CompilerDiagnostic::ReceiverTypeUnestablished { .. } => true, CompilerDiagnostic::ServiceConfigReferenceJudgmentDeferred { .. } => true, diff --git a/src/v1/stage0/src/v1_tests_claim_bare_variant_reference_occurrence_control_test.rs b/src/v1/stage0/src/v1_tests_claim_bare_variant_reference_occurrence_control_test.rs new file mode 100644 index 00000000000..8c28ff13998 --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_bare_variant_reference_occurrence_control_test.rs @@ -0,0 +1,82 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.bare_variant_reference_occurrence_control_test + +pub use crate::std_algebra::FreeMonoid; +use crate::std_occurrence_identity::OccurrenceCategory::{ + CallableOccurrence, ConstructorOccurrence, FieldOccurrence, LexicalValueOccurrence, + MethodOccurrence, NamespaceSegmentOccurrence, TypeOccurrence, +}; +pub use crate::std_occurrence_identity::{OccurrenceCategory, ReferenceOccurrence}; +use crate::std_types::Bool::*; +pub use crate::std_types::{Bool, List}; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::parse_authored_occurrence_binding_source; +pub use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource; +use crate::v1_gunbc_occurrence_binding_parser_walk::ParsedOccurrenceBindingSource::{ + ParsedOccurrenceBindingSourceReady, ParsedOccurrenceBindingSourceRefused, +}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn subject_with_bare_variants() -> String { + thread_local! { + static CACHED: String = { + "module zqxbeta.user\n\ntype ZqxAnswer = ZqxYes | ZqxNo\n\nfn zqx_probe_pick() -> ZqxAnswer {\n let probe = ZqxProbeRed\n ZqxYes\n}\n".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn subject_without_value_references() -> String { + thread_local! { + static CACHED: String = { + "module zqxbeta.user\n\ntype ZqxAnswer = ZqxYes | ZqxNo\n\nfn zqx_probe_count() -> Int {\n 1\n}\n".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn category_is_lexical_value(c: OccurrenceCategory) -> bool { + match c.clone() { + OccurrenceCategory::LexicalValueOccurrence => true, + OccurrenceCategory::TypeOccurrence => false, + OccurrenceCategory::CallableOccurrence => false, + OccurrenceCategory::ConstructorOccurrence => false, + OccurrenceCategory::NamespaceSegmentOccurrence => false, + OccurrenceCategory::FieldOccurrence => false, + OccurrenceCategory::MethodOccurrence => false, + } +} + +pub fn lexical_value_reference_count(source: String) -> i64 { + match (*parse_authored_occurrence_binding_source( + "bare_variant_control.dag".to_string(), + source.clone(), + )) + .clone() + { + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceRefused => (0 - 1), + ParsedOccurrenceBindingSource::ParsedOccurrenceBindingSourceReady { + transport: t, .. + } => { + (Rc::new({ + let mut __result = Vec::new(); + for r in t.references.clone().iter().cloned() { + if category_is_lexical_value(r.category.clone()) { + __result.push(r); + } + } + __result + }) + .len() as i64) + } + } +} + +pub fn bare_nullary_variant_references_are_carried_by_the_parser_transport_holds() -> bool { + ((lexical_value_reference_count(subject_with_bare_variants()) == 2) + && (lexical_value_reference_count(subject_without_value_references()) == 0)) +} diff --git a/src/v1/tests/claim/bare_variant_reference_occurrence_control_test.dag b/src/v1/tests/claim/bare_variant_reference_occurrence_control_test.dag new file mode 100644 index 00000000000..ed9fb768d44 --- /dev/null +++ b/src/v1/tests/claim/bare_variant_reference_occurrence_control_test.dag @@ -0,0 +1,101 @@ +module v1.tests.claim.bare_variant_reference_occurrence_control_test + +import std.types { String, List, Int, Bool } +import std.occurrence_identity { + ReferenceOccurrence, + OccurrenceCategory, + LexicalValueOccurrence, + TypeOccurrence, + CallableOccurrence, + ConstructorOccurrence, + NamespaceSegmentOccurrence, + FieldOccurrence, + MethodOccurrence, +} +import v1.gunbc.occurrence_binding_parser_walk { + ParsedOccurrenceBindingSource, + ParsedOccurrenceBindingSourceReady, + ParsedOccurrenceBindingSourceRefused, + parse_authored_occurrence_binding_source, +} + +// WHAT THIS CONTROL HOLDS, AND WHY IT IS THE ONLY THING IN THE CORPUS THAT HOLDS IT. +// +// The namespace program's baseline census owes an answer for every reference occurrence the +// validated transport carries (`v2.workflow.legacy_binding_observation` `expected_reference_occurrence_ids`), +// and its completeness law is an identity join over exactly that denominator. So whether a class of +// reference is IN the transport decides whether a complete observation is even possible for it. +// +// For BARE NULLARY VARIANT references -- `Absent`, `Empty`, the unit arm of nearly every coproduct -- +// the answer is YES, and it is not obvious from either end: the emitter's use-line collector drops +// exactly this class (`v1.compiler.emit_rust` `collect_value_ref_names` sends ExprVar carrying a +// VariantValueBinding to its `_ => []`), so a reader who starts there concludes the class is invisible +// everywhere. It is not: at PARSE time a bare variant reference is an ordinary `ExprVar` -- binding_kind +// is not yet decided -- and `v1.compiler.parse` `parsed_occurrence_role_for_node` classifies every +// ExprVar as a reference occurrence. The transport therefore carries it. +// +// THE CLAIM THIS PROTECTS IS A NEGATIVE ONE: no transport widening is owed for the class. A negative +// is exactly what rots silently, because nothing downstream fails when it stops being true -- the +// observation simply becomes unable to be complete again, in the same invisible way. If the parser's +// ExprVar classification ever changes, this control going red is how anyone finds out. +// +// WHY IT LIVES IN THE v1 TEST TREE AND EXECUTES ON NO REQUIRED RUN, stated rather than left to be +// discovered: +// the required floor's source roots are `dag` and `src/v2`, and this subject is only reachable through +// `v1.compiler.parse`, so no witness under `dag/test/claim` can call it. The one route a dag-side +// witness has to v1 is reading the file TEXT, which declares ReadsLiveTree and is declined by the fold. +// So this executes when invoked and on no required run. It is a `test fn` under `src/v1/tests/claim` +// rather than a `ProcessExit` entry point for a second, measured reason: as a standalone module it +// entered the regen closure and the fixed point refused for four missing mirrors -- its own plus +// `std.process` and two `extdeps.process.*` -- so hosting a control would have grown the frozen v1 +// seed by four emitted files. A test module emits none. +// +// Rung: mitigatable. Next-rung trigger: a witness home whose source roots reach `src/v1`, or a host +// builtin exposing the parser's transport the way `compile_dag_diagnostic_census` exposes diagnostics. +// +// gunbc run --source-root dag --source-root src/v1 --source-root src/v2 --claim-run \ +// --entry src/v1/tests/claim/bare_variant_reference_occurrence_control_test.dag \ +// --function bare_nullary_variant_references_are_carried_by_the_parser_transport_holds + +// THE SUBJECT. Two bare nullary variant references -- `ZqxProbeRed` in a let initializer and `ZqxYes` +// in tail position -- and one type reference, `ZqxAnswer`. Names are deliberately corpus-unique so the +// source cannot accidentally bind to anything real. +data subject_with_bare_variants: String = "module zqxbeta.user\n\ntype ZqxAnswer = ZqxYes | ZqxNo\n\nfn zqx_probe_pick() -> ZqxAnswer {\n let probe = ZqxProbeRed\n ZqxYes\n}\n" + +// THE CONTROL, and it is what gives the check power to reject. Same shape, same declarations, no value +// reference of any kind in the body. If this one also reported lexical-value references, the assertion +// above would be measuring the parser's willingness to emit occurrences at all rather than its +// treatment of this class. +data subject_without_value_references: String = "module zqxbeta.user\n\ntype ZqxAnswer = ZqxYes | ZqxNo\n\nfn zqx_probe_count() -> Int {\n 1\n}\n" + +fn category_is_lexical_value(c: OccurrenceCategory) -> Bool { + match c { + LexicalValueOccurrence => true + TypeOccurrence => false + CallableOccurrence => false + ConstructorOccurrence => false + NamespaceSegmentOccurrence => false + FieldOccurrence => false + MethodOccurrence => false + } +} + +fn lexical_value_reference_count(source: String) -> Int { + match parse_authored_occurrence_binding_source(file: "bare_variant_control.dag", source: source) { + ParsedOccurrenceBindingSourceRefused => 0 - 1 + ParsedOccurrenceBindingSourceReady { transport: t, module_path: _ } => + t.references + |> filter(r => category_is_lexical_value(c: r.category)) + |> count + } +} + +// THE ASSERTION AND ITS CONTROL IN ONE ROW, deliberately. Read alone, "the subject has two +// lexical-value references" is satisfied by a parser that emits a reference occurrence for +// everything; the control's zero is what makes the two a measurement of THIS class rather than of +// the parser's willingness to emit occurrences at all. A refusal to parse renders as -1 rather than +// as 0, so a broken subject cannot pass as an empty one. +test fn bare_nullary_variant_references_are_carried_by_the_parser_transport_holds() -> Bool { + lexical_value_reference_count(source: subject_with_bare_variants) == 2 + && lexical_value_reference_count(source: subject_without_value_references) == 0 +}