diff --git a/src/v1/00_core.dag b/src/v1/00_core.dag index 49c6e9f138b..5097dca82f7 100644 --- a/src/v1/00_core.dag +++ b/src/v1/00_core.dag @@ -174,6 +174,7 @@ type CompilerDiagnostic | OwnershipViolation { binding: String, fn_name: String, consumers: Int, span: SourceSpan } | VariantCollision { variant: String, enum1: String, enum2: String, span: SourceSpan } | SoleConstructorViolation { type_name: String, span: SourceSpan } + | ConstructorReferenceAdmissionViolation { function_name: String, caller_module: String, owner_module_path: String, admitted: List, span: SourceSpan } | UnlistedImportUse { name: String, span: SourceSpan } | AmbiguousReference { name: String, candidates: List, span: SourceSpan } | CallArgumentNameUnknown { callee: String, argument: String, declared: List, span: SourceSpan } @@ -288,6 +289,7 @@ fn diagnostic_to_span(d: CompilerDiagnostic) -> SourceSpan { OwnershipViolation { binding: _, fn_name: _, consumers: _, span: s } => s VariantCollision { variant: _, enum1: _, enum2: _, span: s } => s SoleConstructorViolation { type_name: _, span: s } => s + ConstructorReferenceAdmissionViolation { function_name: _, caller_module: _, owner_module_path: _, admitted: _, span: s } => s UnlistedImportUse { name: _, span: s } => s AmbiguousReference { name: _, candidates: _, span: s } => s CallArgumentNameUnknown { callee: _, argument: _, declared: _, span: s } => s @@ -328,6 +330,8 @@ fn diagnostic_to_message(d: CompilerDiagnostic) -> String { OwnershipViolation { binding: b, fn_name: f, consumers: c, span: _ } => concat("ownership: binding '", b, "' in '", f, "' has ", to_string(value: c), " consumers") VariantCollision { variant: v, enum1: e1, enum2: e2, span: _ } => concat("variant '", v, "' appears in both '", e1, "' and '", e2, "'") SoleConstructorViolation { type_name: t, span: _ } => concat("sole_constructor type '", t, "' cannot be constructed outside its defining module") + ConstructorReferenceAdmissionViolation { function_name: fname, caller_module: cm, owner_module_path: om, admitted: adm, span: _ } => + concat("function '", fname, "' (declared in '", om, "') declares admit_callers and refuses this call from module '", cm, "' — admitted callers: [", join(adm, separator: ", "), "]") UnlistedImportUse { name: n, span: _ } => concat("unlisted import use '", n, "' (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") CallArgumentNameUnknown { callee: c, argument: a, declared: ds, span: _ } => @@ -931,6 +935,16 @@ fn field_init_node_value(n: Node) -> Node { } } +fn fn_admit_callers(n: Node) -> List? { + match n.properties |> filter(p => p.name == "admit_callers") |> first { + Present { value: p } => + Present { value: field_init_node_value(n: p).children + |> map(c => match expr_literal_string_optional(expr: c) { Present { value: v } => v Absent => "" }) + |> filter(s => s != "") } + Absent => Absent + } +} + fn if_condition(texpr: Node) -> Node { expr_child_at(texpr: texpr, index: 0, role: "if condition") } fn if_then_branch(texpr: Node) -> Node { expr_child_at(texpr: texpr, index: 1, role: "if then-branch") } fn if_else_branch(texpr: Node) -> Node? { texpr.children |> skip(2) |> first } diff --git a/src/v1/02_parse.dag b/src/v1/02_parse.dag index 3a21d602f29..942a060a452 100644 --- a/src/v1/02_parse.dag +++ b/src/v1/02_parse.dag @@ -274,6 +274,7 @@ type ArgResult { arg: Node, tokens: TokenStream, ctx: ParseContext, err: ErrorNo type FieldResult { field: Node, tokens: TokenStream, ctx: ParseContext, err: ErrorNode? } type FieldInitResult { field: Node, tokens: TokenStream, ctx: ParseContext, err: ErrorNode? } type ResUseResult { resource_use: Node, tokens: TokenStream, ctx: ParseContext, err: ErrorNode? } +type AdmitCallersResult { props: List, tokens: TokenStream, ctx: ParseContext, err: ErrorNode? } type ItemPrefixResult { name: String @@ -2448,6 +2449,17 @@ fn parse_fn_after_kw(tokens: TokenStream, ctx: ParseContext, start_span: SourceS ItemResult { item: item, tokens: skip_newlines(tokens: r.tokens), ctx: r.ctx, err: none } } +fn parse_optional_admit_callers(tokens: TokenStream, ctx: ParseContext) -> AdmitCallersResult { + let tokens = skip_newlines(tokens: tokens) + if tok_is_ident_text(tok: token_stream_first(stream: tokens), text: "admit_callers") { + let r = parse_field_init(tokens: tokens, ctx: ctx) + if has_err(err: r.err) { return AdmitCallersResult { props: [], tokens: r.tokens, ctx: r.ctx, err: r.err } } + AdmitCallersResult { props: [r.field], tokens: skip_newlines(tokens: r.tokens), ctx: r.ctx, err: none } + } else { + AdmitCallersResult { props: [], tokens: tokens, ctx: ctx, err: none } + } +} + fn parse_fn_body_from_prefix(prefix: ItemPrefixResult, start_span: SourceSpan) -> ItemResult { let name = prefix.name let name_span = prefix.name_span @@ -2460,6 +2472,11 @@ fn parse_fn_body_from_prefix(prefix: ItemPrefixResult, start_span: SourceSpan) - let named_dummy = Node { name: name, span: start_span, ident_span: Present { value: name_span }, children: [], params: all_params, inferred: none, return_cardinality: Required, uses: [], body: none, connective: NoConnective, transport: none, properties: [], type_annotation: none, is_self_recursive: false, has_non_tail_self_call: false, match_pattern: none, expr_data: NoExprData } let tokens = skip_newlines(tokens: prefix.tokens) + let admit_r = parse_optional_admit_callers(tokens: tokens, ctx: ctx) + if has_err(err: admit_r.err) { return ItemResult { item: named_dummy, tokens: admit_r.tokens, ctx: admit_r.ctx, err: admit_r.err } } + let admit_props = admit_r.props + let tokens = admit_r.tokens + let ctx = admit_r.ctx let body_result = if tok_is_eq(tok: token_stream_first(stream: tokens)) { let r = expect(tokens: tokens, expected: ExpectEq) if has_err(err: r.err) { return ItemResult { item: named_dummy, tokens: r.tokens, ctx: ctx, err: r.err } } @@ -2476,7 +2493,7 @@ fn parse_fn_body_from_prefix(prefix: ItemPrefixResult, start_span: SourceSpan) - let item = Node { name: name, span: start_span, ident_span: Present { value: name_span }, children: [], params: all_params, inferred: inferred, return_cardinality: Required, uses: [], body: Present { value: body }, connective: NoConnective, transport: none, - properties: [], type_annotation: none, + properties: admit_props, type_annotation: none, is_self_recursive: false, has_non_tail_self_call: false, match_pattern: none, expr_data: NoExprData } ItemResult { item: item, tokens: skip_newlines(tokens: body_result.tokens), ctx: body_result.ctx, err: none } } diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index e96e518c65e..58c9ff79e36 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -31,7 +31,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, UnresolvedType, FieldNotFound, MethodNotFound, MethodExistenceUndecided, MethodExistenceFrontierAdmitted, ReceiverTypeUnestablished, FrontierOccurrenceBudgetExceeded, MissingField, AmbiguousReference, + InternalError, TypeMismatch, VariantCollision, SoleConstructorViolation, ConstructorReferenceAdmissionViolation, UnresolvedType, FieldNotFound, MethodNotFound, MethodExistenceUndecided, MethodExistenceFrontierAdmitted, ReceiverTypeUnestablished, FrontierOccurrenceBudgetExceeded, MissingField, AmbiguousReference, CallArgumentNameUnknown, CallPositionalSurplus, resource_use_name_at, resource_use_resource, is_child_accessor_in_model, is_tree_size_reducing, is_property_contraction, @@ -140,7 +140,7 @@ import v1.compiler.infer_resolve { import v1.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv, ResolveFuncSigsResult, resolve_func_sigs, flatten_parent_envs, - FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous + FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous, FuncSigCallerNotAdmitted } import v1.compiler.infer_emit_info { TypeRepr, StructRepr, EnumRepr, @@ -606,6 +606,13 @@ fn ambiguous_reference_refusal(name: String, candidates: List, span: Sou } } +fn constructor_reference_admission_refusal(function_name: String, owner_module_path: String, admitted: List, span: SourceSpan, scope: InferScope) -> InferResult { + InferResult { + typed: semantic_expr_error_node(message: concat("function '", function_name, "' refuses this call — not in its admit_callers list"), span: span), + diagnostics: [make_error_node(diagnostic: ConstructorReferenceAdmissionViolation { function_name: function_name, caller_module: scope.module_name, owner_module_path: owner_module_path, admitted: admitted, span: span }, module_name: scope.module_name)] + } +} + fn type_mismatch_error(expected: String, got: String, span: SourceSpan, module_name: String) -> ErrorNode { make_error_node(diagnostic: TypeMismatch { expected: expected, got: got, span: span }, module_name: module_name) } @@ -2483,6 +2490,8 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu } FuncSigAmbiguous { candidates: ambiguous_fn_candidates } => ambiguous_reference_refusal(name: name, candidates: ambiguous_fn_candidates, span: span, scope: scope) + FuncSigCallerNotAdmitted { function_name: adm_fname, owner_module_path: adm_owner, admitted: adm_admitted } => + constructor_reference_admission_refusal(function_name: adm_fname, owner_module_path: adm_owner, admitted: adm_admitted, span: span, scope: scope) FuncSigUnresolved => match lookup_binding_by_name(env: scope.type_env, name: name) { Present { value: gbinding } => @@ -2657,6 +2666,8 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu match call_sig_lookup { FuncSigAmbiguous { candidates: ambiguous_fn_candidates } => ambiguous_reference_refusal(name: func_name, candidates: ambiguous_fn_candidates, span: span, scope: scope) + FuncSigCallerNotAdmitted { function_name: adm_fname, owner_module_path: adm_owner, admitted: adm_admitted } => + constructor_reference_admission_refusal(function_name: adm_fname, owner_module_path: adm_owner, admitted: adm_admitted, span: span, scope: scope) _ => let sig = func_sig_if_resolved(lookup: call_sig_lookup) let sig_params = match sig { diff --git a/src/v1/04_lookup.dag b/src/v1/04_lookup.dag index e693f0ecacc..9ab5ac0cb68 100644 --- a/src/v1/04_lookup.dag +++ b/src/v1/04_lookup.dag @@ -47,7 +47,7 @@ import v1.compiler.infer_emit_info { import v1.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv, lookup_resolved_sig, - FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous + FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous, FuncSigCallerNotAdmitted } import v1.compiler.infer_service { @@ -81,11 +81,9 @@ fn lookup_func_sig(func_env: ResolvedFuncEnv, type_env: TypeEnv, name: String) - match lookup_resolved_sig(env: func_env, name: name) { FuncSigResolved { sig: sig } => FuncSigResolved { sig: sig } FuncSigAmbiguous { candidates: cands } => FuncSigAmbiguous { candidates: cands } - FuncSigUnresolved => - match func_sig_from_global_bare(type_env: type_env, name: name) { - Present { value: sig } => FuncSigResolved { sig: sig } - Absent => FuncSigUnresolved - } + FuncSigCallerNotAdmitted { function_name: fname, owner_module_path: om, admitted: adm } => + FuncSigCallerNotAdmitted { function_name: fname, owner_module_path: om, admitted: adm } + FuncSigUnresolved => func_sig_from_global_bare(type_env: type_env, name: name) } } @@ -94,6 +92,7 @@ fn func_sig_if_resolved(lookup: FuncSigLookup) -> ResolvedFuncSig? { FuncSigResolved { sig: sig } => Present { value: sig } FuncSigUnresolved => Absent FuncSigAmbiguous { candidates: _ } => Absent + FuncSigCallerNotAdmitted { function_name: _, owner_module_path: _, admitted: _ } => Absent } } @@ -126,12 +125,12 @@ fn borrowed_census_decl(type_env: TypeEnv, name: String) -> BorrowedCensusDecl? data census_reserved_method_name_note: String = "Census precedence law (main-parity): the whole-pool census never serves an algebra method-template name (filter/any/contains/…) as a bare fn sig or tail callable. Those names belong to the known-method bridge in the ExprCall arm — a census sig for the UNLOADED v2.std.algebra typed filter(xs, f) as a plain call and the runtime died NoSuchFunction where main rewrote to the builtin method. Loaded providers are unaffected: lookup_resolved_sig runs before the census fallback." -fn func_sig_from_global_bare(type_env: TypeEnv, name: String) -> ResolvedFuncSig? { +fn func_sig_from_global_bare(type_env: TypeEnv, name: String) -> FuncSigLookup { if algebra_method_template_name(name: name) { - return none - } + FuncSigUnresolved + } else { match infer_builtin_call_type(name: name) { - Present { value: _ } => none + Present { value: _ } => FuncSigUnresolved Absent => let borrowed = match lookup_binding_by_name_local(env: type_env, name: name) { Present { value: binding } => @@ -142,7 +141,7 @@ fn func_sig_from_global_bare(type_env: TypeEnv, name: String) -> ResolvedFuncSig Present { value: bd } => let node = bd.node match (node.params |> count) > 0 || node.inferred != none || node.type_annotation != none { - false => Absent + false => FuncSigUnresolved true => let excluded = borrowed_generic_param_names(params: node.params, source_indices: type_env.source_indices) let raw_return = match node.inferred { @@ -153,7 +152,7 @@ fn func_sig_from_global_bare(type_env: TypeEnv, name: String) -> ResolvedFuncSig } } if bd.owner_module_path == type_env.module_path { - Present { value: ResolvedFuncSig { + FuncSigResolved { sig: ResolvedFuncSig { name: name, params: node.params, inferred: raw_return, @@ -162,18 +161,36 @@ fn func_sig_from_global_bare(type_env: TypeEnv, name: String) -> ResolvedFuncSig variant_provenance: empty_map() } } } else { - let qualified_return = qualify_borrowed_type_names(n: raw_return, owner_module_path: bd.owner_module_path, env: type_env, excluded: excluded) - Present { value: ResolvedFuncSig { - name: name, - params: node.params, - inferred: qualified_return, - is_async: false, - output_provenance: [], - variant_provenance: empty_map() - } } + match fn_admit_callers(n: node) { + Present { value: admitted } => + if admitted |> any(a => a == type_env.module_path) { + let qualified_return = qualify_borrowed_type_names(n: raw_return, owner_module_path: bd.owner_module_path, env: type_env, excluded: excluded) + FuncSigResolved { sig: ResolvedFuncSig { + name: name, + params: node.params, + inferred: qualified_return, + is_async: false, + output_provenance: [], + variant_provenance: empty_map() + } } + } else { + FuncSigCallerNotAdmitted { function_name: name, owner_module_path: bd.owner_module_path, admitted: admitted } + } + Absent => + let qualified_return = qualify_borrowed_type_names(n: raw_return, owner_module_path: bd.owner_module_path, env: type_env, excluded: excluded) + FuncSigResolved { sig: ResolvedFuncSig { + name: name, + params: node.params, + inferred: qualified_return, + is_async: false, + output_provenance: [], + variant_provenance: empty_map() + } } + } } } - Absent => Absent + Absent => FuncSigUnresolved + } } } } diff --git a/src/v1/04_sigs.dag b/src/v1/04_sigs.dag index 0282664ae77..dcd35cae35d 100644 --- a/src/v1/04_sigs.dag +++ b/src/v1/04_sigs.dag @@ -79,6 +79,7 @@ type FuncSigLookup = FuncSigResolved { sig: ResolvedFuncSig } | FuncSigUnresolved | FuncSigAmbiguous { candidates: List } + | FuncSigCallerNotAdmitted { function_name: String, owner_module_path: String, admitted: List } type ChainSigScan { first_sig: ResolvedFuncSig?