w; w.Value.Flag` -- `Value` is declared `T`, and without binding
// T to Settings the access resolves and the type of the RESULT is the type parameter,
diff --git a/graph/csharp/engine/expression-resolution/overload-args.dl b/graph/csharp/engine/expression-resolution/overload-args.dl
index 6489ae95..7c65128b 100644
--- a/graph/csharp/engine/expression-resolution/overload-args.dl
+++ b/graph/csharp/engine/expression-resolution/overload-args.dl
@@ -163,6 +163,123 @@ ovl_rejected(prov, e, m) :-
ovl_arg_type(prov, e, k, at), ovl_param_type(m, k, pt),
!ovl_conv(at, pt).
+// ── 1b. NOT APPLICABLE, BY SHAPE ────────────────────────────────────────────
+// Three more readings the arguments make certain, each tested against the parameter
+// the argument lands on (an extension method called on a receiver shifted by one):
+//
+// a LAMBDA converts only to a delegate (or an expression of one): never to a
+// predefined type, and to Func<..> / Action<..> / Expression<..> only
+// with the delegate's parameter count. `Must(x => ..)` against
+// Must(Func) / Must(Func) keeps the first.
+// a PREDEFINED value (a literal, or a name declared with a predefined type) never
+// converts to a delegate, to a non-`params` array, or to a type in
+// source that declares no conversion operator, nor to an exception.
+// a SOURCE-TYPED argument (its type a class or struct in source) never converts to a
+// delegate, to a predefined type, or to another source type it neither
+// derives from nor implements, when neither declares a conversion.
+//
+// Prune-only, and the site is never emptied (ovl_survives below).
+ovl_ext_receiver(prov, e) :- call_receiver_expr(prov, e, r), !ref_names_type(prov, r, _).
+ovl_param_at(prov, e, m, k, p) :-
+ ovl_site(prov, e), ovl_cand(prov, e, m), !method_is_extension(_, m),
+ param_decl(_, m, pos, _, p), k = to_number(pos), param_mode(_, p, "VALUE"), !param_is_params(_, p).
+ovl_param_at(prov, e, m, k, p) :-
+ ovl_site(prov, e), ovl_cand(prov, e, m), method_is_extension(_, m), ovl_ext_receiver(prov, e),
+ param_decl(_, m, pos, _, p), k = to_number(pos) - 1, k >= 0, param_mode(_, p, "VALUE"), !param_is_params(_, p).
+ovl_param_at(prov, e, m, k, p) :-
+ ovl_site(prov, e), ovl_cand(prov, e, m), method_is_extension(_, m), !ovl_ext_receiver(prov, e),
+ param_decl(_, m, pos, _, p), k = to_number(pos), param_mode(_, p, "VALUE"), !param_is_params(_, p).
+
+ovl_delegate_name("Func"). ovl_delegate_name("Action"). ovl_delegate_name("Predicate").
+ovl_delegate_name("Expression"). ovl_delegate_name("Comparison"). ovl_delegate_name("Converter").
+// the delegate a parameter's type names, and how many parameters it takes
+ovl_param_delegate(p, tr) :- param_type_ref(_, p, tr), type_ref_name(_, tr, n), ovl_delegate_name(n), n != "Expression".
+ovl_param_delegate(p, ar) :- param_type_ref(_, p, tr), type_ref_name(_, tr, "Expression"), type_ref_arg(_, tr, "0", ar).
+ovl_delegate_arity(tr, n) :- type_ref_name(_, tr, "Func"), type_ref_argc(_, tr, c), n = to_number(c) - 1.
+ovl_delegate_arity(tr, n) :- type_ref_name(_, tr, "Action"), type_ref_argc(_, tr, c), n = to_number(c).
+ovl_delegate_arity(tr, 1) :- type_ref_name(_, tr, "Predicate").
+ovl_delegate_arity(tr, 2) :- type_ref_name(_, tr, "Comparison").
+ovl_delegate_arity(tr, 1) :- type_ref_name(_, tr, "Converter").
+ovl_param_is_delegate(p) :- param_type_ref(_, p, tr), type_ref_name(_, tr, n), ovl_delegate_name(n).
+ovl_param_is_delegate(p) :- param_type_ref(_, p, tr), type_ref_resolves(_, tr, gk), type_group(_, t, gk), type_decl(_, _, _, _, "DELEGATE", t).
+
+ovl_lambda_arg(prov, e, k, n) :-
+ ovl_arg_at(prov, e, k, a), expr_anon_decl(prov, a, lam),
+ n = count : { param_decl(prov, lam, _, _, _) }.
+ovl_rejected(prov, e, m) :-
+ ovl_lambda_arg(prov, e, k, n), ovl_param_at(prov, e, m, k, p),
+ ovl_param_delegate(p, tr), ovl_delegate_arity(tr, d), d != n.
+ovl_rejected(prov, e, m) :-
+ ovl_lambda_arg(prov, e, k, _), ovl_param_at(prov, e, m, k, p),
+ param_type_name(_, p, _, ctn), ovl_predef(ctn, _).
+
+// a parameter type no predefined value converts to
+ovl_param_closed(p) :- ovl_param_is_delegate(p).
+ovl_param_closed(p) :- param_type_name(_, p, _, ctn), strlen(ctn) > 2, substr(ctn, max(0, strlen(ctn) - 2), 2) = "[]".
+ovl_param_closed(p) :- param_type_name(_, p, tn, _), strlen(tn) >= 9, substr(tn, max(0, strlen(tn) - 9), 9) = "Exception".
+ovl_param_closed(p) :- param_type_ref(_, p, tr), type_ref_resolves(_, tr, gk), ovl_source_closed(gk).
+// a class or struct in source with no conversion operator of its own
+ovl_source_closed(gk) :-
+ type_group("client", t, gk), type_decl("client", _, _, _, tk, t), (tk = "CLASS" ; tk = "STRUCT" ; tk = "RECORD"),
+ !ovl_type_converts(gk).
+ovl_type_converts(gk) :- method_in_type(_, gk, m), method_kind(_, m, "CONVERSION_OPERATOR").
+ovl_rejected(prov, e, m) :-
+ ovl_arg_type(prov, e, k, _), ovl_param_at(prov, e, m, k, p), ovl_param_closed(p).
+
+// a source-typed argument
+ovl_arg_source(prov, e, k, gk) :-
+ ovl_arg_at(prov, e, k, a), !expr_anon_decl(prov, a, _), expr_type(prov, a, gk), ovl_source_closed(gk).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_source(prov, e, k, _), ovl_param_at(prov, e, m, k, p), ovl_param_is_delegate(p).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_source(prov, e, k, _), ovl_param_at(prov, e, m, k, p),
+ param_type_name(_, p, _, ctn), ovl_predef(ctn, _).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_source(prov, e, k, gk), ovl_param_at(prov, e, m, k, p),
+ param_type_ref(_, p, tr), type_ref_resolves(_, tr, pgk), type_group("client", _, pgk),
+ !type_self_or_ancestor(prov, gk, pgk), !type_satisfies(prov, gk, pgk), !ovl_type_converts(pgk).
+
+// An INTERFACE-typed argument converts only to that interface, one it extends, or
+// object: never to a class or struct in source, a delegate or a predefined type.
+ovl_arg_iface(prov, e, k, gk) :-
+ ovl_arg_at(prov, e, k, a), !expr_anon_decl(prov, a, _), expr_type(prov, a, gk),
+ type_group("client", t, gk), type_is_interface("client", t).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_iface(prov, e, k, _), ovl_param_at(prov, e, m, k, p), ovl_param_is_delegate(p).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_iface(prov, e, k, _), ovl_param_at(prov, e, m, k, p),
+ param_type_name(_, p, _, ctn), ovl_predef(ctn, _).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_iface(prov, e, k, _), ovl_param_at(prov, e, m, k, p),
+ param_type_ref(_, p, tr), type_ref_resolves(_, tr, pgk), ovl_source_closed(pgk).
+ovl_rejected(prov, e, m) :-
+ ovl_arg_iface(prov, e, k, gk), ovl_param_at(prov, e, m, k, p),
+ param_type_ref(_, p, tr), type_ref_resolves(_, tr, pgk), pgk != gk,
+ type_group("client", pt, pgk), type_is_interface("client", pt),
+ !type_self_or_ancestor(prov, gk, pgk), !type_satisfies(prov, gk, pgk), !interface_extends_tc(prov, gk, pgk).
+interface_extends_tc(prov, a, b) :- interface_extends(prov, a, b).
+interface_extends_tc(prov, a, c) :- interface_extends(prov, a, b), interface_extends_tc(prov, b, c).
+
+// A LAMBDA never converts to a class, struct, interface or enum declared in source, to
+// an array, or to the reflection types an overload commonly pairs it with
+// (`Map(m => m.Id)` against `Map(Type, MemberInfo)`).
+ovl_rejected(prov, e, m) :-
+ ovl_lambda_arg(prov, e, k, _), ovl_param_at(prov, e, m, k, p),
+ param_type_ref(_, p, tr), type_ref_resolves(_, tr, pgk),
+ type_group("client", t, pgk), !type_is_delegate("client", t).
+ovl_rejected(prov, e, m) :-
+ ovl_lambda_arg(prov, e, k, _), ovl_param_at(prov, e, m, k, p),
+ param_type_name(_, p, tn, ctn), (ovl_reflection_type(tn) ; (strlen(ctn) > 2, substr(ctn, max(0, strlen(ctn) - 2), 2) = "[]")).
+ovl_reflection_type("Type"). ovl_reflection_type("MemberInfo"). ovl_reflection_type("PropertyInfo").
+ovl_reflection_type("FieldInfo"). ovl_reflection_type("MethodInfo"). ovl_reflection_type("ParameterInfo").
+ovl_reflection_type("ConstructorInfo").
+
+// EXPLICIT TYPE ARGUMENTS fix the generic arity: `GetField(0)` is one of the
+// one-type-parameter overloads, never GetField(int) or GetField(int).
+ovl_rejected(prov, e, m) :-
+ ovl_cand(prov, e, m), call_type_argc(prov, e, n), n != "0", n != "",
+ method_type_arity(_, m, a), a != n, !method_is_extension(_, m).
+
// ── 2. EXACT MATCH ──────────────────────────────────────────────────────────
// Every argument identical to its parameter, as many parameters as arguments (no
// default filled in, no params expansion), and not generic (a type parameter that
diff --git a/graph/csharp/engine/framework-behavior/destinations.dl b/graph/csharp/engine/framework-behavior/destinations.dl
index 4c7a0e87..06ada5b9 100644
--- a/graph/csharp/engine/framework-behavior/destinations.dl
+++ b/graph/csharp/engine/framework-behavior/destinations.dl
@@ -48,9 +48,7 @@ call_recv_written_name(e, n) :-
// …and a property read off another value, `services.Mediator.Send(..)`: a minimal-API
// endpoint takes its services as one parameter object, and the receiver is that
// object's property, whose declared type name is what the project wrote.
-call_recv_written_name(e, n) :-
- call_receiver_expr("client", e, r), property_access("client", r, p, _),
- property_type_name("client", p, n, _), n != "".
+// (recv_value_written_name below)
// …and a `var` from the service locator, which holds its type argument
call_recv_written_name(e, n) :-
call_receiver_expr("client", e, r),
@@ -59,6 +57,47 @@ call_recv_written_name(e, n) :-
type_ref_owner("client", tr, init, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
!type_ref_parent("client", tr, _, _, _), type_ref_name("client", tr, n).
+// The same shapes with no local in between, and a local holding a member read. Each names
+// the type the project WROTE at the value's source:
+// `new Pricing.PricingClient(ch).Quote(..)` the created type
+// `factory.CreateClient("o").Place(..)` the type argument of a
+// service locator or client factory
+// `Shared.Client.GetAsync(..)`, `Shared.Field.GetAsync(..)` a property's or field's declared type
+// `var c = Shared.Client; c.GetAsync(..)` the same, through a `var`
+// A client factory is its own knob (cs_client_factory_generic), not cs_di_resolve, which
+// also names messaging receivers.
+recv_value_written_name(x, n) :- expr_new_type_ref("client", x, tr), type_ref_name("client", tr, n).
+recv_value_written_name(x, n) :- csite("client", x), call_callee_name("client", x, f), cs_di_resolve(f),
+ type_ref_owner("client", tr, x, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
+ !type_ref_parent("client", tr, _, _, _), type_ref_name("client", tr, n).
+recv_value_written_name(x, n) :- csite("client", x), call_callee_name("client", x, f), cs_client_factory_generic(f),
+ type_ref_owner("client", tr, x, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
+ !type_ref_parent("client", tr, _, _, _), type_ref_name("client", tr, n).
+recv_value_written_name(x, n) :- property_access("client", x, p, _), property_type_name("client", p, n, _), n != "".
+recv_value_written_name(x, n) :- expr_kind("client", x, "MEMBER_ACCESS"),
+ expr_qualifier_child("client", x, q), expr_member_name_child("client", x, nm), expr_written_name("client", nm, fn),
+ ref_names_type("client", q, gk), member_lookup("client", gk, fn, "field", f),
+ field_type_name("client", f, n, _), n != "".
+recv_value_written_name(x, n) :- expr_kind("client", x, "MEMBER_ACCESS"),
+ expr_qualifier_child("client", x, q), expr_member_name_child("client", x, nm), expr_written_name("client", nm, fn),
+ expr_type("client", q, gk), member_lookup("client", gk, fn, "field", f),
+ field_type_name("client", f, n, _), n != "".
+call_recv_written_name(e, n) :- call_receiver_expr("client", e, r), recv_value_written_name(r, n).
+call_recv_written_name(e, n) :-
+ call_receiver_expr("client", e, r),
+ ref_denotes("client", r, "LOCAL_VARIABLE", v), var_is_implicit("client", v),
+ var_value_initializer("client", v, init), recv_value_written_name(init, n).
+call_recv_written_name(e, n) :-
+ call_receiver_expr("client", e, r),
+ ref_denotes("client", r, "LOCAL_VARIABLE", v), var_is_implicit("client", v),
+ var_value_initializer("client", v, init), ref_denotes("client", init, "PROPERTY", p),
+ property_type_name("client", p, n, _), n != "".
+call_recv_written_name(e, n) :-
+ call_receiver_expr("client", e, r),
+ ref_denotes("client", r, "LOCAL_VARIABLE", v), var_is_implicit("client", v),
+ var_value_initializer("client", v, init), ref_denotes("client", init, "FIELD", f),
+ field_type_name("client", f, n, _), n != "".
+
// ─────────────────────────────────────────────────────────────────────────────
// 2. gRPC
// ─────────────────────────────────────────────────────────────────────────────
@@ -97,12 +136,22 @@ grpc_service_of_name(n, svc, sfx) :- grpc_name_segs(n, svc, tail), svc != "",
// ── the server end ──────────────────────────────────────────────────────────
// A method a service class OVERRIDES from `.Base` serves `/`. Only an
// override: every rpc is a virtual on the generated base, and a helper written beside
-// the rpcs is not one.
-grpc_serves(m, d) :-
- heritage_slot("client", t, _, bn, _, _),
- cs_grpc_server_suffix(sfx), grpc_service_of_name(bn, svc, sfx),
- method_owner("client", t, m), method_modifier("client", m, "override"),
- method_name("client", m, n), d = cat(svc, cat("/", n)).
+// the rpcs is not one. The base is written `.Base` (optionally qualified), or
+// `Base` alone after `using static .;`, and the service class may reach it
+// through the project's own bases (`StockService : AuditedStockBase : Stock.StockBase`).
+// grpc_base_at(T0, Svc): the type whose heritage WRITES the generated base.
+grpc_base_at(t0, svc) :- heritage_slot("client", t0, _, bn, _, _),
+ cs_grpc_server_suffix(sfx), grpc_service_of_name(bn, svc, sfx).
+grpc_base_at(t0, svc) :- grpc_static_base(t0, svc, _).
+grpc_static_base(t0, svc, st) :- heritage_slot("client", t0, _, bn, _, _), !contains(".", bn),
+ type_module("client", t0, mod), using_static_type("client", mod, st), last_segment(st, svc),
+ cs_grpc_server_suffix(sfx), bn = cat(svc, sfx).
+last_segment_demand(st) :- using_static_type("client", _, st).
+grpc_service_type(gk, t0, svc) :- grpc_base_at(t0, svc), type_group("client", t0, gk).
+grpc_service_type(gk, t0, svc) :- type_ancestor("client", gk, agk), grpc_base_at(t0, svc), type_group("client", t0, agk).
+grpc_serves_at(m, d, t0) :- grpc_service_type(gk, t0, svc), method_in_type("client", gk, m),
+ method_modifier("client", m, "override"), method_name("client", m, n), d = cat(svc, cat("/", n)).
+grpc_serves(m, d) :- grpc_serves_at(m, d, _).
// ── the client end ──────────────────────────────────────────────────────────
// A call on a receiver written `.Client`. The call name is the rpc, or the rpc
@@ -129,9 +178,73 @@ grpc_send_primary(e, from, d) :- grpc_send_site(e, from, svc, n),
grpc_name_ends_async(n) :- grpc_send_site(_, _, _, n), cs_grpc_async_suffix(a),
strlen(n) > strlen(a), substr(n, strlen(n) - strlen(a), strlen(a)) = a.
+// ── which namespace each end's `` can be in ──────────────────────────────
+// The generated `` is never in source, so each end is read for the namespaces its
+// written name could resolve in: a qualifier it wrote (`Widgets.Rpc.Catalog.CatalogBase`,
+// relative to each enclosing namespace too), the namespace a `using static` names, or,
+// unqualified, the enclosing namespaces and the file's and the project's `using`s. Two
+// services called Catalog in two namespaces are then told apart by whether the client's
+// namespaces and the server's meet. Framework namespaces (System, Microsoft) and the
+// gRPC runtime's own never hold a generated service and are not evidence.
+grpc_ns_demand(ns) :- grpc_base_at(t0, _), type_namespace("client", t0, ns).
+grpc_ns_demand(ns) :- grpc_send_name(e, _), grpc_send_ns_home(e, ns).
+grpc_ns_dot(ns, i) :- grpc_ns_demand(ns), i = range(0, strlen(ns)), substr(ns, i, 1) = ".".
+grpc_ns_prefix(ns, ns) :- grpc_ns_demand(ns), ns != "".
+grpc_ns_prefix(ns, p) :- grpc_ns_dot(ns, i), p = substr(ns, 0, i).
+grpc_name_prefix(n, q) :- grpc_name_prev_dot(n, p), q = substr(n, 0, p).
+grpc_global_using(ns) :- using_decl("client", _, ns, _, u), using_is_global("client", u),
+ !using_is_static("client", u), !using_is_alias("client", u).
+
+// the server: T0 is the type whose heritage names the base
+grpc_written_base(t0, bn) :- heritage_slot("client", t0, _, bn, _, _), cs_grpc_server_suffix(sfx),
+ grpc_service_of_name(bn, _, sfx).
+grpc_srv_ns(t0, q) :- grpc_written_base(t0, bn), grpc_name_prefix(bn, q).
+grpc_srv_ns(t0, cat(p, cat(".", q))) :- grpc_written_base(t0, bn), grpc_name_prefix(bn, q),
+ type_namespace("client", t0, ns), grpc_ns_prefix(ns, p).
+grpc_srv_ns(t0, p) :- grpc_written_base(t0, bn), !grpc_name_has_prev(bn),
+ type_namespace("client", t0, ns), grpc_ns_prefix(ns, p).
+grpc_srv_ns(t0, u) :- grpc_written_base(t0, bn), !grpc_name_has_prev(bn),
+ type_module("client", t0, mod), using_namespace("client", mod, u).
+grpc_srv_ns(t0, u) :- grpc_written_base(t0, bn), !grpc_name_has_prev(bn), grpc_global_using(u).
+grpc_srv_ns(t0, q) :- grpc_static_base(t0, _, st), grpc_name_last_dot(st, l), q = substr(st, 0, l).
+grpc_name_demand(st) :- using_static_type("client", _, st), contains(".", st).
+
+// the client: the name its receiver wrote, and the namespace the calling method is in
+grpc_send_name(e, rn) :- call_recv_written_name(e, rn), cs_grpc_client_suffix(sfx),
+ grpc_service_of_name(rn, _, sfx), csite("client", e).
+grpc_send_ns_home(e, ns) :- grpc_send_name(e, _), call_from("client", e, from),
+ method_owner("client", t, from), type_namespace("client", t, ns).
+grpc_cli_ns(e, q) :- grpc_send_name(e, rn), grpc_name_prefix(rn, q).
+grpc_cli_ns(e, cat(p, cat(".", q))) :- grpc_send_name(e, rn), grpc_name_prefix(rn, q),
+ grpc_send_ns_home(e, ns), grpc_ns_prefix(ns, p).
+grpc_cli_ns(e, p) :- grpc_send_name(e, rn), !grpc_name_has_prev(rn), grpc_send_ns_home(e, ns), grpc_ns_prefix(ns, p).
+grpc_cli_ns(e, u) :- grpc_send_name(e, rn), !grpc_name_has_prev(rn), call_module("client", e, mod),
+ using_namespace("client", mod, u).
+grpc_cli_ns(e, u) :- grpc_send_name(e, rn), !grpc_name_has_prev(rn), grpc_global_using(u).
+grpc_ns_not_evidence(ns) :- grpc_cli_ns(_, ns), cs_grpc_runtime_namespace(ns).
+grpc_ns_not_evidence(ns) :- grpc_cli_ns(_, ns), cs_framework_namespace_root(r), ns = r.
+grpc_ns_not_evidence(ns) :- grpc_cli_ns(_, ns), cs_framework_namespace_root(r),
+ strlen(ns) > strlen(r), substr(ns, 0, strlen(r) + 1) = cat(r, ".").
+
+// ── the join ────────────────────────────────────────────────────────────────
+// One service of that name: [exact], as before. Several: those whose namespaces meet the
+// client's stay [exact] and the others are dropped; when none meets (the namespace
+// cannot be read), every one is kept, one rung down, at `service_name`.
+grpc_pair(e, from, to, d, t0) :- grpc_send_cand(e, from, d), grpc_serves_at(to, d, t0), from != to.
+grpc_pair_t0(e, d, t0) :- grpc_pair(e, _, _, d, t0).
+grpc_pair_n(e, d, k) :- grpc_pair_t0(e, d, _), k = count : { grpc_pair_t0(e, d, _) }.
+grpc_pair_scoped(e, d, t0) :- grpc_pair_t0(e, d, t0), grpc_cli_ns(e, ns), !grpc_ns_not_evidence(ns),
+ grpc_srv_ns(t0, ns).
+grpc_has_scoped(e, d) :- grpc_pair_scoped(e, d, _).
+remote_edge_via(e, from, to, tr, d, conf) :-
+ cs_remote_transport_grpc(tr), cs_remote_confidence_exact(conf),
+ grpc_pair(e, from, to, d, t0), grpc_pair_n(e, d, 1).
remote_edge_via(e, from, to, tr, d, conf) :-
cs_remote_transport_grpc(tr), cs_remote_confidence_exact(conf),
- grpc_send_cand(e, from, d), grpc_serves(to, d), from != to.
+ grpc_pair(e, from, to, d, t0), grpc_pair_n(e, d, k), k > 1, grpc_pair_scoped(e, d, t0).
+remote_edge_via(e, from, to, tr, d, conf) :-
+ cs_remote_transport_grpc(tr), cs_remote_confidence_service_name(conf),
+ grpc_pair(e, from, to, d, _), grpc_pair_n(e, d, k), k > 1, !grpc_has_scoped(e, d).
remote_send_at(e, from, tr, d) :- cs_remote_transport_grpc(tr), grpc_send_primary(e, from, d).
serves_destination(m, tr, d) :- cs_remote_transport_grpc(tr), grpc_serves(m, d).
@@ -229,17 +342,147 @@ http_route_raw(m, p2, verb) :- ctl_route_c(m, p, verb), tok_at_a(p, i), method_n
p2 = cat(substr(p, 0, i), cat(n, substr(p, i + 8, strlen(p) - i - 8))).
http_route_raw(m, p, verb) :- ctl_route_c(m, p, verb), !contains("[action]", p).
+// ── the server: MVC controllers and their actions ───────────────────────────
+// An action is a public instance method of a controller: a non-abstract class named
+// `Controller` or deriving from Controller/ControllerBase, not [NonAction], not an
+// override of the framework's own virtuals.
+mvc_ctl(t, c) :- type_decl("client", tn, _, _, "CLASS", t), !type_modifier("client", t, "abstract"),
+ !type_modifier("client", t, "static"), cs_http_controller_suffix(sfx), ends_with_suffix(tn, sfx),
+ c = substr(tn, 0, strlen(tn) - strlen(sfx)).
+mvc_ctl(t, tn) :- type_decl("client", tn, _, _, "CLASS", t), !type_modifier("client", t, "abstract"),
+ !type_modifier("client", t, "static"), cs_http_controller_suffix(sfx), !ends_with_suffix(tn, sfx),
+ heritage_slot("client", t, _, bn, _, _), last_segment(bn, b), cs_http_controller_base(b).
+mvc_non_action(m) :- attr_on(m, "METHOD", n, _), cs_http_non_action_attr(n).
+mvc_action(t, m) :- mvc_ctl(t, _), method_owner("client", t, m), method_kind("client", m, "METHOD"),
+ member_access("client", m, "PUBLIC"), !method_is_static("client", m),
+ !method_modifier("client", m, "override"), !mvc_non_action(m).
+
+// Attribute routing, the class's route with no route on the action: every action of a
+// `[Route("[controller]/[action]")]` class is served there, for any verb. Only when the
+// class template names [action], so each action has a route of its own; a public method
+// under a template without it would share its sibling's route, and is more often a
+// helper someone forgot to mark [NonAction] than a route anyone calls.
+ctl_action(m, "", verb) :- mvc_action(t, m), ctl_base(t, b), contains("[action]", b),
+ !method_has_verb(m), !method_has_route_tmpl(m), cs_http_verb_any(verb).
+
+// ── the server: conventional routes ─────────────────────────────────────────
+// `app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}")` routes
+// every action that has no attribute route: its class has no [Route], it has no template
+// of its own, and it is not in an [ApiController] (which requires attribute routing).
+// The route is the pattern with {controller} and {action} (and {area}) filled in; a verb
+// attribute with no template narrows the verb. A pattern that does not spell
+// {controller} or {action} takes it from the call's defaults, `new { controller = "X" }`.
+conv_route_call(e, k) :- call_callee_name("client", e, n), cs_http_conventional_route(n, k), csite("client", e).
+conv_pattern(e, p) :- conv_route_call(e, k), http_arg_string(e, k, p).
+conv_pattern(e, p) :- call_callee_name("client", e, n), cs_http_default_route(n, p), csite("client", e).
+conv_pattern(e, p) :- call_callee_name("client", e, n), cs_http_area_route(n, _, k), csite("client", e),
+ http_arg_string(e, k, p).
+conv_area_fixed(e, a) :- call_callee_name("client", e, n), cs_http_area_route(n, k, _), csite("client", e),
+ http_arg_string(e, k, a).
+// a default: `defaults: new { controller = "Reports" }`. The parser writes an anonymous
+// object's members as its children in order, each name followed by its value.
+// (demanded by the callee's name, not by conv_pattern: the pattern's value is a string
+// read through str_value, and so are these)
+conv_call(e) :- conv_route_call(e, _).
+conv_call(e) :- call_callee_name("client", e, n), cs_http_default_route(n, _), csite("client", e).
+conv_call(e) :- call_callee_name("client", e, n), cs_http_area_route(n, _, _), csite("client", e).
+conv_default_lit(e, key, raw) :- conv_call(e), http_arg(e, _, a), expr_kind("client", a, "ANONYMOUS_OBJECT"),
+ expr_child("client", a, _, kp, k), expr_written_name("client", k, key), cs_http_route_value_key(key),
+ expr_child("client", a, _, vp, val), to_number(vp) = to_number(kp) + 1, expr_literal("client", val, _, raw).
+str_demand(raw) :- conv_default_lit(_, _, raw).
+conv_default(e, key, v) :- conv_default_lit(e, key, raw), str_value(raw, v).
+
+// where `{name` opens a token in a pattern, and the `}` that closes it
+conv_tok_at(p, nm, i) :- conv_pattern(_, p), cs_http_route_value_key(nm), tok = cat("{", nm),
+ contains(tok, p), i = range(0, strlen(p) - strlen(tok)), substr(p, i, strlen(tok)) = tok,
+ cs_route_token_end(substr(p, i + strlen(tok), 1)).
+conv_has(p, nm) :- conv_tok_at(p, nm, _).
+conv_tok_span(p, nm, i, j) :- conv_tok_at(p, nm, i),
+ j = min k : { k = range(i, strlen(p)), substr(p, k, 1) = "}" }.
+
+mvc_type_attr_routed(t) :- ctl_has_base(t).
+mvc_type_attr_routed(t) :- attr_on(t, "TYPE", n, _), cs_http_api_controller_attr(n).
+mvc_method_attr_routed(m) :- method_has_route_tmpl(m).
+mvc_method_attr_routed(m) :- method_verb_attr(m, _, a), attr_has_template(a).
+mvc_area(t, a) :- attr_on(t, "TYPE", n, at), cs_http_area_attr(n), attr_template(at, a).
+mvc_has_area(t) :- mvc_area(t, _).
+conv_action(t, m, c, mn) :- mvc_action(t, m), mvc_ctl(t, c), !mvc_type_attr_routed(t), !mvc_method_attr_routed(m),
+ method_name("client", m, mn).
+conv_ctl_ok(e, p, t) :- conv_pattern(e, p), conv_has(p, "controller"), conv_action(t, _, _, _).
+conv_ctl_ok(e, p, t) :- conv_pattern(e, p), !conv_has(p, "controller"), conv_default(e, "controller", c),
+ conv_action(t, _, c, _).
+conv_act_ok(e, p, m) :- conv_pattern(e, p), conv_has(p, "action"), conv_action(_, m, _, _).
+conv_act_ok(e, p, m) :- conv_pattern(e, p), !conv_has(p, "action"), conv_default(e, "action", a),
+ conv_action(_, m, _, a).
+conv_area_ok(e, p, t, "") :- conv_pattern(e, p), !conv_has(p, "area"), !conv_area_fixed(e, _),
+ conv_action(t, _, _, _), !mvc_has_area(t).
+conv_area_ok(e, p, t, a) :- conv_pattern(e, p), conv_has(p, "area"), !conv_area_fixed(e, _), mvc_area(t, a).
+conv_area_ok(e, p, t, a) :- conv_pattern(e, p), conv_area_fixed(e, a), mvc_area(t, a).
+conv_use(p, m, c, mn, a) :- conv_action(t, m, c, mn), conv_ctl_ok(e, p, t), conv_act_ok(e, p, m),
+ conv_area_ok(e, p, t, a).
+
+// fill the tokens in turn: controller, then action, then area
+conv_s1(p, c, s) :- conv_use(p, _, c, _, _), conv_tok_span(p, "controller", i, j),
+ s = cat(substr(p, 0, i), cat(c, substr(p, j + 1, strlen(p) - j - 1))).
+conv_s1(p, c, p) :- conv_use(p, _, c, _, _), !conv_has(p, "controller").
+conv_s2_demand(s, mn) :- conv_use(p, _, c, mn, _), conv_s1(p, c, s).
+conv_s2_at(s, i) :- conv_s2_demand(s, _), tok = "{action", contains(tok, s),
+ i = range(0, strlen(s) - strlen(tok)), substr(s, i, strlen(tok)) = tok, cs_route_token_end(substr(s, i + 7, 1)).
+conv_s2_has(s) :- conv_s2_at(s, _).
+conv_s2(s, mn, o) :- conv_s2_demand(s, mn), conv_s2_at(s, i), j = min k : { k = range(i, strlen(s)), substr(s, k, 1) = "}" },
+ o = cat(substr(s, 0, i), cat(mn, substr(s, j + 1, strlen(s) - j - 1))).
+conv_s2(s, mn, s) :- conv_s2_demand(s, mn), !conv_s2_has(s).
+conv_s3_demand(o, a) :- conv_use(p, _, c, mn, a), conv_s1(p, c, s), conv_s2(s, mn, o).
+conv_s3_at(s, i) :- conv_s3_demand(s, _), tok = "{area", contains(tok, s),
+ i = range(0, strlen(s) - strlen(tok)), substr(s, i, strlen(tok)) = tok, cs_route_token_end(substr(s, i + 5, 1)).
+conv_s3_has(s) :- conv_s3_at(s, _).
+conv_s3(s, a, o) :- conv_s3_demand(s, a), conv_s3_at(s, i), j = min k : { k = range(i, strlen(s)), substr(s, k, 1) = "}" },
+ o = cat(substr(s, 0, i), cat(a, substr(s, j + 1, strlen(s) - j - 1))).
+conv_s3(s, a, s) :- conv_s3_demand(s, a), !conv_s3_has(s).
+conv_route(m, r) :- conv_use(p, m, c, mn, a), conv_s1(p, c, s1), conv_s2(s1, mn, s2), conv_s3(s2, a, r).
+conv_verb(m, verb) :- conv_route(m, _), method_verb_attr(m, verb, _).
+conv_verb(m, verb) :- conv_route(m, _), !method_has_verb(m), cs_http_verb_any(verb).
+http_route_raw(m, r, verb) :- conv_route(m, r), conv_verb(m, verb).
+
// ── the server: minimal APIs ────────────────────────────────────────────────
-map_call(e, verb) :- call_callee_name("client", e, n), cs_http_map_verb(n, verb), csite("client", e).
+map_verb_call(e, verb) :- call_callee_name("client", e, n), cs_http_map_verb(n, verb), csite("client", e).
+map_methods_call(e) :- call_callee_name("client", e, n), cs_http_map_methods(n), csite("client", e).
map_group_call(e) :- call_callee_name("client", e, n), cs_http_map_group(n), csite("client", e).
+// MapMethods: the verbs a collection spells, `new[] { "HEAD", "OPTIONS" }`, `["PUT"]`,
+// `new List { ... }` or `new[] { HttpMethods.Head }`, inline or in a local.
+// A list none of whose elements reads as a verb serves any verb, like a bare [Route].
+map_verbs_expr(e, l) :- map_methods_call(e), http_arg(e, 1, l).
+map_verbs_expr(e, i) :- map_methods_call(e), http_arg(e, 1, l),
+ ref_denotes("client", l, "LOCAL_VARIABLE", v), var_value_initializer("client", v, i).
+map_verb_elem(e, x) :- map_verbs_expr(e, l), expr_child("client", l, _, _, init),
+ expr_kind("client", init, "INITIALIZER"), expr_child("client", init, _, _, x).
+map_verb_elem(e, x) :- map_verbs_expr(e, l), expr_child("client", l, "COLLECTION_ELEMENT", _, x).
+str_demand(raw) :- map_verb_elem(_, x), expr_literal("client", x, _, raw).
+map_methods_verb(e, verb) :- map_verb_elem(e, x), expr_literal("client", x, _, raw), str_value(raw, verb),
+ cs_http_method_member(_, verb).
+map_methods_verb(e, verb) :- map_verb_elem(e, x), expr_kind("client", x, "MEMBER_ACCESS"),
+ expr_member_name_child("client", x, nm), expr_written_name("client", nm, mn), cs_http_method_member(mn, verb).
+map_methods_has_verb(e) :- map_methods_verb(e, _).
+
+map_call(e, verb) :- map_verb_call(e, verb).
+map_call(e, verb) :- map_methods_verb(e, verb).
+map_call(e, verb) :- map_methods_call(e), !map_methods_has_verb(e), cs_http_verb_any(verb).
+
+// the handler is argument 1, or 2 on MapMethods; written there, or held in a local
+map_handler_at(e, 1) :- map_verb_call(e, _).
+map_handler_at(e, 2) :- map_methods_call(e).
+map_handler_expr(e, h) :- map_handler_at(e, k), http_arg(e, k, h).
+map_handler_expr(e, i) :- map_handler_at(e, k), http_arg(e, k, h),
+ ref_denotes("client", h, "LOCAL_VARIABLE", v), var_value_initializer("client", v, i).
+
// the handler: a method group, `Type.Method`, or a lambda (its own method row)
-map_handler(e, m) :- map_call(e, _), http_arg(e, 1, h), expr_anon_decl("client", h, m).
-map_handler(e, m) :- map_call(e, _), http_arg(e, 1, h),
+map_handler(e, m) :- map_handler_expr(e, h), expr_anon_decl("client", h, m).
+map_handler(e, m) :- map_handler_expr(e, h),
expr_kind("client", h, "NAME_REFERENCE"), expr_ref_unknown("client", h),
expr_written_name("client", h, n), !local_binds("client", h, _),
expr_ultimate_type_group("client", h, gk), member_lookup("client", gk, n, "method", m).
-map_handler(e, m) :- map_call(e, _), http_arg(e, 1, h),
+map_handler(e, m) :- map_handler_expr(e, h),
expr_kind("client", h, "MEMBER_ACCESS"),
expr_qualifier_child("client", h, q), expr_member_name_child("client", h, nm),
expr_written_name("client", nm, n), delegate_qualifier_type("client", q, gk),
@@ -253,7 +496,7 @@ gp_demand(init) :- gp_demand(r), ref_denotes("client", r, "LOCAL_VARIABLE", v),
gp_demand(rr) :- gp_demand(r), csite("client", r), call_receiver_expr("client", r, rr).
gp_is_local(r) :- gp_demand(r), ref_denotes("client", r, "LOCAL_VARIABLE", v), var_initializer("client", v, _).
gp_is_call(r) :- gp_demand(r), csite("client", r), call_receiver_expr("client", r, _).
-group_prefix(r, "") :- gp_demand(r), !gp_is_local(r), !gp_is_call(r).
+group_prefix(r, "") :- gp_demand(r), !gp_is_local(r), !gp_is_call(r), !gp_is_param(r).
group_prefix(r, p) :- gp_demand(r), ref_denotes("client", r, "LOCAL_VARIABLE", v),
var_initializer("client", v, init), group_prefix(init, p).
group_prefix(r, p) :- gp_demand(r), csite("client", r), !map_group_call(r),
@@ -261,9 +504,46 @@ group_prefix(r, p) :- gp_demand(r), csite("client", r), !map_group_call(r),
group_prefix(r, g) :- gp_demand(r), map_group_call(r), http_arg_string(r, 0, g),
call_receiver_expr("client", r, rr), group_prefix(rr, "").
group_prefix(r, p) :- gp_demand(r), map_group_call(r), http_arg_string(r, 0, g),
- call_receiver_expr("client", r, rr), group_prefix(rr, pp), pp != "", !starts_slash(g), p = cat(pp, cat("/", g)).
+ call_receiver_expr("client", r, rr), group_prefix(rr, pp), pp != "", strlen(pp) < 512, !starts_slash(g),
+ p = cat(pp, cat("/", g)).
group_prefix(r, p) :- gp_demand(r), map_group_call(r), http_arg_string(r, 0, g),
- call_receiver_expr("client", r, rr), group_prefix(rr, pp), pp != "", starts_slash(g), p = cat(pp, g).
+ call_receiver_expr("client", r, rr), group_prefix(rr, pp), pp != "", strlen(pp) < 512, starts_slash(g),
+ p = cat(pp, g).
+// (the length bound only stops a helper that maps a sub-group of itself from recursing)
+
+// A group handed to a helper: `app.MapGroup("/api/orders").MapOrderEndpoints()` with
+// `MapOrderEndpoints(this RouteGroupBuilder g) { g.MapGet(...); }`. The parameter takes
+// the prefix of the builder at EACH call site, so a helper mapped under two groups
+// serves under both. A call binds the helper when it resolved to it, or, since the
+// builder type is never staged and such a call resolves to nothing, when it names the
+// one route-builder extension of that name and arity in scope. Without a bound call
+// the parameter is a root, as before.
+gp_bind_param(w, p, k) :- param_decl("client", w, pos, _, p), param_type_name("client", p, tn, _),
+ cs_http_route_builder_type(tn), k = to_number(pos).
+gp_w_nparams(w, n) :- gp_bind_param(w, _, _), n = count : { param_decl("client", w, _, _, _) }.
+gp_ext_named(w, n, mod) :- gp_bind_param(w, p, 0), extension_method("client", w, p),
+ method_name("client", w, n), extension_in_scope("client", mod, w).
+gp_named_call(c, n, mod) :- call_callee_name("client", c, n), gp_ext_named(_, n, mod), csite("client", c),
+ call_module("client", c, mod), call_receiver_expr("client", c, _), !expr_resolves_to_method("client", c, _).
+gp_c_nargs(c, n) :- gp_named_call(c, _, _), n = count : { arg_index(c, _, _) }.
+gp_c_nargs(c, n) :- gp_bind_param(w, _, _), expr_resolves_to_method("client", c, w), csite("client", c),
+ n = count : { arg_index(c, _, _) }.
+gp_named_cand(c, w) :- gp_named_call(c, n, mod), gp_ext_named(w, n, mod), gp_c_nargs(c, na), gp_w_nparams(w, na + 1).
+gp_named_ncand(c, k) :- gp_named_cand(c, _), k = count : { gp_named_cand(c, _) }.
+gp_helper_call(c, w) :- gp_bind_param(w, _, _), expr_resolves_to_method("client", c, w), csite("client", c).
+gp_helper_call(c, w) :- gp_named_cand(c, w), gp_named_ncand(c, 1).
+// extension form: the receiver is parameter 0 and argument k-1 is parameter k
+gp_bind_arg(p, a) :- gp_helper_call(c, w), extension_method("client", w, _), gp_c_nargs(c, na),
+ gp_w_nparams(w, na + 1), gp_bind_param(w, p, 0), call_receiver_expr("client", c, a).
+gp_bind_arg(p, a) :- gp_helper_call(c, w), extension_method("client", w, _), gp_c_nargs(c, na),
+ gp_w_nparams(w, na + 1), gp_bind_param(w, p, k), k > 0, arg_index(c, k - 1, a).
+// a plain or statically written call: argument k is parameter k
+gp_bind_arg(p, a) :- gp_helper_call(c, w), gp_c_nargs(c, na), gp_w_nparams(w, na),
+ gp_bind_param(w, p, k), arg_index(c, k, a).
+gp_param_arg(r, a) :- gp_demand(r), ref_denotes("client", r, "PARAMETER", p), gp_bind_arg(p, a).
+gp_demand(a) :- gp_param_arg(_, a).
+gp_is_param(r) :- gp_param_arg(r, _).
+group_prefix(r, p) :- gp_param_arg(r, a), group_prefix(a, p).
http_route_raw(m, p, verb) :- map_call(e, verb), map_handler(e, m), http_arg_string(e, 0, t),
call_receiver_expr("client", e, r), group_prefix(r, ""), p = t.
diff --git a/graph/csharp/engine/framework-behavior/dispatch.dl b/graph/csharp/engine/framework-behavior/dispatch.dl
index 8b8e07a1..c5311ef1 100644
--- a/graph/csharp/engine/framework-behavior/dispatch.dl
+++ b/graph/csharp/engine/framework-behavior/dispatch.dl
@@ -155,3 +155,32 @@ med_dispatch(e, m) :- med_declared_handler(e, m).
med_send_matcher(e) :- med_send_arg(e, a), call_callee_name("client", a, n0), last_segment(n0, n), cs_mock_arg_matcher(n).
call_chain_edge(e, from, "-", m, "client", "event_dispatch", kind) :- med_send_site(e, from, _, _), med_dispatch(e, m),
!med_send_matcher(e), from != m, invocation_site("client", e, kind).
+
+// ── A CLASS MOCK RUNS ITS TYPE'S CONSTRUCTOR ────────────────────────────────
+// `new Mock(2m)` and `Substitute.ForPartsOf(2m)` construct a proxy
+// subclass of PriceRule, and the proxy's constructor calls PriceRule(decimal) with the
+// arguments given. The site's own target is the library's (Mock's constructor,
+// ForPartsOf), so `impact` on the constructor left those tests out and `path` called the
+// two independent (#1495). The hop is ADDED beside the site's row, on the tier a
+// framework-run hop takes (event_dispatch), as the mediator's is above; the site's own
+// resolution is unchanged. The overload is chosen by argument count, as for `new C(args)`.
+// Knobs: cs_mock_new_type, cs_mock_factory, cs_mock_behavior_arg.
+mock_ctor_site(e, gk) :- expr_new_type_ref("client", e, tr), type_ref_name("client", tr, n0), last_segment(n0, n),
+ cs_mock_new_type(n), type_ref_arg("client", tr, "0", ar), type_ref_resolves("client", ar, gk).
+mock_ctor_site(e, gk) :- call_receiver_text("client", e, rn0), last_segment(rn0, rn), call_callee_name("client", e, sn),
+ cs_mock_factory(rn, sn), call_type_argc("client", e, "1"),
+ type_ref_owner("client", tr, e, "EXPRESSION"), type_ref_context("client", tr, "METHOD_TYPE_ARGUMENT"),
+ !type_ref_parent("client", tr, _, _, _), type_ref_resolves("client", tr, gk).
+last_segment_demand(n) :- expr_new_type_ref("client", _, tr), type_ref_name("client", tr, n).
+last_segment_demand(n) :- call_receiver_text("client", e, n), call_callee_name("client", e, sn), cs_mock_factory(_, sn).
+// `new Mock(() => new C(1))` hands Moq the construction to run: that lambda's own
+// `new` is the call, and the site passes no constructor arguments.
+mock_ctor_lambda(e) :- mock_ctor_site(e, _), arg_index(e, 0, a), expr_kind("client", a, "LAMBDA").
+mock_ctor_behavior(e) :- mock_ctor_site(e, _), arg_index(e, 0, a), expr_written_name("client", a, w),
+ cs_mock_behavior_arg(b), strlen(w) > strlen(b), substr(w, 0, strlen(b) + 1) = cat(b, ".").
+mock_ctor_argc(e, ac) :- mock_ctor_site(e, _), !mock_ctor_lambda(e), !mock_ctor_behavior(e), call_argc("client", e, ac).
+mock_ctor_argc(e, ac) :- mock_ctor_site(e, _), !mock_ctor_lambda(e), mock_ctor_behavior(e), call_argc("client", e, ac0),
+ ac = to_string(to_number(ac0) - 1).
+mock_ctor(e, ctor) :- mock_ctor_site(e, gk), mock_ctor_argc(e, ac), type_ctor("client", gk, ctor), ctor_accepts_argc("client", ctor, ac).
+call_chain_edge(e, from, "-", ctor, "client", "event_dispatch", kind) :- mock_ctor(e, ctor), call_from("client", e, from),
+ from != ctor, invocation_site("client", e, kind).
diff --git a/graph/csharp/engine/framework-behavior/entry-points.dl b/graph/csharp/engine/framework-behavior/entry-points.dl
index 36d582dd..759405a3 100644
--- a/graph/csharp/engine/framework-behavior/entry-points.dl
+++ b/graph/csharp/engine/framework-behavior/entry-points.dl
@@ -11,8 +11,13 @@
// about which method is the handler.
// ============================================================================
-// main: a static Main, and the method top-level statements compile to
-entry_point(m, "main") :- method_name("client", m, "Main"), method_modifier("client", m, "static").
+// main: a static Main of a shape C# accepts as an entry point (void, int, Task or Task,
+// with no parameter or one string[]), and the method top-level statements compile to
+entry_point(m, "main") :- method_name("client", m, "Main"), method_modifier("client", m, "static"), cs_main_shape(m).
+cs_main_shape(m) :- method_name("client", m, "Main"), method_return_type_name("client", m, rt), cs_main_return(rt),
+ method_paramc("client", m, "0").
+cs_main_shape(m) :- method_name("client", m, "Main"), method_return_type_name("client", m, rt), cs_main_return(rt),
+ method_paramc("client", m, "1"), param_decl("client", m, "0", _, p), param_type_name("client", p, _, pt), cs_main_param(pt).
entry_point(m, "main") :- method_is_top_level_entry("client", m).
// test: a test method, and the set-up and tear-down around it
@@ -20,6 +25,15 @@ entry_point(m, "test") :- attr_on(m, "METHOD", n, _), cs_test_attr(n).
entry_point(m, "test") :- attr_on(m, "METHOD", n, _), cs_test_lifecycle_attr(n).
entry_point(m, "test") :- attr_on(m, "METHOD", n0, _), cs_attr_suffix(n0, n), cs_test_attr(n).
entry_point(m, "test") :- attr_on(m, "METHOD", n0, _), cs_attr_suffix(n0, n), cs_test_lifecycle_attr(n).
+// an attribute class DERIVED from a test attribute is one: xUnit reads `[SlowFact]` through
+// `SlowFactAttribute : FactAttribute` as a fact (#1497), transitively. Matched by the base's written name, since
+// the framework is not in the source; an attribute derived from anything else (`AuditedAttribute : Attribute`)
+// stays out.
+cs_test_attr_type(cat(n, "Attribute")) :- cs_test_attr(n).
+cs_test_attr_type(n0) :- type_decl("client", n0, _, _, _, t), heritage_slot("client", t, _, bn0, _, _),
+ last_segment(bn0, bn), cs_test_attr_type(bn).
+entry_point(m, "test") :- attr_on(m, "METHOD", n0, _), cs_test_attr_type(n0).
+entry_point(m, "test") :- attr_on(m, "METHOD", n0, _), cs_test_attr_type(cat(n0, "Attribute")).
// `[FactAttribute]` is the same attribute as `[Fact]`
cs_attr_suffix(n0, n) :- attr_on(_, "METHOD", n0, _), strlen(n0) > 9,
substr(n0, strlen(n0) - 9, 9) = "Attribute", n = substr(n0, 0, strlen(n0) - 9).
diff --git a/graph/csharp/engine/framework-behavior/knobs.dl b/graph/csharp/engine/framework-behavior/knobs.dl
index 1ead8281..1a27058d 100644
--- a/graph/csharp/engine/framework-behavior/knobs.dl
+++ b/graph/csharp/engine/framework-behavior/knobs.dl
@@ -26,8 +26,23 @@ cs_grpc_client_member_not_rpc("NewInstance").
cs_framework_namespace_root("System").
cs_framework_namespace_root("Microsoft").
+// The gRPC runtime's own namespaces. A file `using` them says nothing about which
+// namespace a generated service is in, so they never tell two same-named services apart.
+cs_grpc_runtime_namespace("Grpc.Core").
+cs_grpc_runtime_namespace("Grpc.Net.Client").
+cs_grpc_runtime_namespace("Grpc.Net.ClientFactory").
+cs_grpc_runtime_namespace("Grpc.AspNetCore.Server").
+cs_grpc_runtime_namespace("Google.Protobuf").
+cs_grpc_runtime_namespace("Google.Protobuf.WellKnownTypes").
+// A factory whose type argument is the client it returns:
+// `factory.CreateClient("orders")` (Grpc.Net.ClientFactory).
+cs_client_factory_generic("CreateClient").
+
cs_remote_transport_grpc("grpc").
cs_remote_confidence_exact("exact").
+// a gRPC send whose service name several services share, none in a namespace the
+// client can see: every one is kept, a rung below exact
+cs_remote_confidence_service_name("service_name").
// ── HTTP: ASP.NET Core on the server ────────────────────────────────────────
// Attribute routing. `Route` carries a template and no verb; each Http carries a
@@ -44,12 +59,38 @@ cs_http_verb_attr("HttpOptions", "OPTIONS"). cs_http_verb_attr("HttpOptionsAttri
// The suffix ASP.NET strips from a controller's class name for the [controller] token.
cs_http_controller_suffix("Controller").
// Minimal APIs: `app.MapGet(template, handler)`, and a route group's prefix.
+// Controllers: the bases that make a class a controller whatever its name, the attribute
+// that keeps a public method from being an action, an [Area], and [ApiController], which
+// requires attribute routing (conventional routes never reach it).
+cs_http_controller_base("Controller").
+cs_http_controller_base("ControllerBase").
+cs_http_non_action_attr("NonAction"). cs_http_non_action_attr("NonActionAttribute").
+cs_http_area_attr("Area"). cs_http_area_attr("AreaAttribute").
+cs_http_api_controller_attr("ApiController"). cs_http_api_controller_attr("ApiControllerAttribute").
+// Conventional routes: the method and the argument its pattern is in. The default route
+// has its pattern fixed; an area route names the area in one argument, the pattern in another.
+cs_http_conventional_route("MapControllerRoute", 1).
+cs_http_conventional_route("MapRoute", 1).
+cs_http_area_route("MapAreaControllerRoute", 1, 2).
+cs_http_area_route("MapAreaRoute", 1, 2).
+cs_http_default_route("MapDefaultControllerRoute", "{controller=Home}/{action=Index}/{id?}").
+// the route values a pattern or its defaults name, and what may follow a token's name
+cs_http_route_value_key("controller"). cs_http_route_value_key("action"). cs_http_route_value_key("area").
+cs_route_token_end("}"). cs_route_token_end("="). cs_route_token_end(":"). cs_route_token_end("?").
cs_http_map_verb("MapGet", "GET").
cs_http_map_verb("MapPost", "POST").
cs_http_map_verb("MapPut", "PUT").
cs_http_map_verb("MapDelete", "DELETE").
cs_http_map_verb("MapPatch", "PATCH").
cs_http_map_group("MapGroup").
+// `app.MapMethods(template, verbs, handler)`: the verbs are a collection in argument 1,
+// each a literal or a `HttpMethods.Head` member (cs_http_method_member below), and the
+// handler is argument 2.
+cs_http_map_methods("MapMethods").
+// The builder types an endpoint-mapping helper takes, `this RouteGroupBuilder g`: the
+// prefix of the group each call site passes carries into the helper's MapX calls.
+cs_http_route_builder_type("RouteGroupBuilder").
+cs_http_route_builder_type("IEndpointRouteBuilder").
// ── HTTP: the client ────────────────────────────────────────────────────────
// A receiver whose written type is HttpClient, and the verb each of its calls sends.
@@ -234,6 +275,26 @@ cs_test_lifecycle_attr("OneTimeSetUp"). cs_test_lifecycle_attr("OneTimeTearD
cs_test_lifecycle_attr("TestInitialize"). cs_test_lifecycle_attr("TestCleanup").
cs_test_lifecycle_attr("ClassInitialize"). cs_test_lifecycle_attr("ClassCleanup").
cs_test_lifecycle_attr("AssemblyInitialize"). cs_test_lifecycle_attr("AssemblyCleanup").
+// MSTest 3.10+: run around EVERY test of the assembly (#1503)
+cs_test_lifecycle_attr("GlobalTestInitialize"). cs_test_lifecycle_attr("GlobalTestCleanup").
+
+// cs_main_return(Written) / cs_main_param(Written): the shapes C# accepts as a program's
+// entry point. A static `Main` of any other shape, `static string Main()` on a helper, is
+// an ordinary method its callers reach, and was a main entry point (#1451).
+cs_main_return("void"). cs_main_return("int"). cs_main_return("Int32"). cs_main_return("System.Int32").
+cs_main_return("Task"). cs_main_return("Task"). cs_main_return("Task").
+cs_main_return("System.Threading.Tasks.Task"). cs_main_return("System.Threading.Tasks.Task").
+cs_main_param("string[]"). cs_main_param("String[]"). cs_main_param("System.String[]").
+
+// cs_mock_new_type(Type) / cs_mock_factory(Receiver, Method): building a CLASS mock runs the
+// mocked type's constructor with the arguments given, through the proxy subclass the
+// library generates: `new Mock(args)` (Moq) and `Substitute.ForPartsOf(args)`,
+// `Substitute.For(args)` (NSubstitute). cs_mock_behavior_arg is a leading argument
+// that configures the mock and is not passed on (Moq's `MockBehavior.Strict`) (#1495).
+cs_mock_new_type("Mock").
+cs_mock_factory("Substitute", "ForPartsOf").
+cs_mock_factory("Substitute", "For").
+cs_mock_behavior_arg("MockBehavior").
// cs_framework_callback(Base, Method, Reason): a framework calls Method on a type that
// derives from Base or implements it, directly or through the project's own bases.
// Base is the written simple name without type arguments. Method "*" is every public
diff --git a/graph/csharp/engine/projections/expressions.dl b/graph/csharp/engine/projections/expressions.dl
index 46e28c76..8a82e1ba 100644
--- a/graph/csharp/engine/projections/expressions.dl
+++ b/graph/csharp/engine/projections/expressions.dl
@@ -117,6 +117,15 @@ expr_written_name("lib", e, n) :-
lib_cs_expression(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, n, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
n != "".
+// ── expr_type_argc(Prov, ExprHash, TypeArgumentCount) ──────────────────────
+// The type arguments written on a name: `Cache` is the name `Cache` at arity 1.
+// A generic name used as a receiver or qualifier is resolved at this arity, since
+// `Cache` and `Cache` are two types.
+expr_type_argc("client", e, tac) :-
+ cs_expression(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, tac, _, _, _, _, _, _, _, _, _, _, _, _, e).
+expr_type_argc("lib", e, tac) :-
+ lib_cs_expression(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, tac, _, _, _, _, _, _, _, _, _, _, _, _, e).
+
// ── expr_literal(Prov, ExprHash, LiteralKind, LiteralValue) ─────────────────
expr_literal("client", e, lk, lv) :-
cs_expression(_, _, _, _, _, _, _, _, _, _, lk, lv, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
diff --git a/graph/csharp/engine/projections/variables.dl b/graph/csharp/engine/projections/variables.dl
index 27be6bf5..841c1f86 100644
--- a/graph/csharp/engine/projections/variables.dl
+++ b/graph/csharp/engine/projections/variables.dl
@@ -61,6 +61,10 @@ var_initializer("lib", v, e) :-
// The initializer IS a value of the variable's type.
var_value_initializer(prov, v, e) :- var_initializer(prov, v, e), var_decl(prov, _, "LOCAL", _, v).
var_value_initializer(prov, v, e) :- var_initializer(prov, v, e), var_decl(prov, _, "CONST", _, v).
+// A `using` resource -- `using (var w = new Writer())` and `using var w = ...;` -- is
+// the disposable itself, so its initializer is a value of its type exactly as a
+// local's is. Without it every call on such a resource has an untyped receiver.
+var_value_initializer(prov, v, e) :- var_initializer(prov, v, e), var_decl(prov, _, "USING", _, v).
// The initializer is a COLLECTION and the variable takes its element type.
var_iterated_initializer(prov, v, e) :- var_initializer(prov, v, e), var_decl(prov, _, "FOREACH", _, v).
diff --git a/graph/csharp/engine/resolution/delegate-params.dl b/graph/csharp/engine/resolution/delegate-params.dl
new file mode 100644
index 00000000..c1b5a58a
--- /dev/null
+++ b/graph/csharp/engine/resolution/delegate-params.dl
@@ -0,0 +1,113 @@
+// ============================================================================
+// RESOLUTION · A DELEGATE HANDED TO A PARAMETER
+//
+// delegates.dl follows a function stored in a field or property and stops at the
+// two shapes it names as NOT HANDLED: a delegate that reaches the member FROM A
+// PARAMETER (`_fn = fn`, `this.a = a`, a primary constructor's `fn` read in a field
+// initializer), and a parameter of delegate type INVOKED DIRECTLY (`act(1)`,
+// `handler.Invoke(call)`, `validatorProvider(x)` inside a lambda that captured it).
+// Both are the ordinary way C# hands behaviour to a library -- a strategy object's
+// constructor, a builder's `Add(v => ...)`, a callback argument -- and in both the
+// function that runs is a lambda or method group written at the CALL SITE, which no
+// member lookup can see.
+//
+// THE SAME ENVELOPE delegates.dl uses, keyed on the parameter rather than the member:
+//
+// delegate_param_holds(P, F) F may be the function parameter P holds: a lambda,
+// an anonymous method or a method group passed as the
+// argument at P's ordinal to a call the engine
+// resolved to P's method (a constructor too); or a
+// parameter of the caller passed on, or a member that
+// holds functions passed on.
+// delegate_member_holds(M, F) gains: F reaches member M through a parameter that
+// is stored in it.
+// a call through P an edge to ONE node per parameter,
+// generated:#..Invoke
+// and method_dispatch_candidate(node, F, "value") for
+// each F it holds -- what impact and path already walk
+// for a member (F -> node -> whoever calls through P).
+//
+// Value flow is not context-sensitive here, exactly as for a member: every function
+// passed to P at any call is a candidate at every call through P. The resolved site
+// target is untouched (Roslyn binds `act(1)` to Invoke), so the per-site score cannot
+// move.
+// ============================================================================
+
+// ── THE ARGUMENT AT AN ORDINAL ──────────────────────────────────────────────
+// Among a call's ARGUMENT children, with no named argument at the site (a named one
+// may bind anywhere). A call the engine resolved to a method, or a `new`.
+delegate_arg_at(e, k, a) :-
+ expr_argument("client", e, pos, a), !call_has_named_args("client", e),
+ p = to_number(pos),
+ k = count : { expr_argument("client", e, q, _), to_number(q) < p }.
+delegate_site_callee(e, m, 0) :-
+ expr_resolves_to_method("client", e, m), !method_is_extension("client", m).
+// An extension method called on a receiver takes the receiver as parameter 0.
+delegate_site_callee(e, m, 1) :-
+ expr_resolves_to_method("client", e, m), method_is_extension("client", m),
+ call_receiver_expr("client", e, _).
+delegate_site_callee(e, m, 0) :-
+ expr_resolves_to_method("client", e, m), method_is_extension("client", m),
+ !call_receiver_expr("client", e, _).
+delegate_site_callee(e, ctor, 0) :- call_ctor_target("client", e, ctor).
+
+delegate_param_at(m, k, p) :-
+ param_decl("client", m, pos, _, p), k = to_number(pos), !param_is_params("client", p).
+
+// delegate_param_receives(P, Arg): Arg is passed to parameter P, and the branches
+// inside it (`?:`, `??`, a cast, parentheses).
+delegate_param_receives(p, a) :-
+ delegate_arg_at(e, k, a), delegate_site_callee(e, m, off), delegate_param_at(m, k + off, p).
+delegate_param_receives(p, c) :- delegate_param_receives(p, a), delegate_value_branch("client", a, c).
+
+// ── A REFERENCE TO A PARAMETER ──────────────────────────────────────────────
+delegate_param_ref(e, p) :- ref_denotes("client", e, "PARAMETER", p).
+// A primary constructor's parameter read in a FIELD or PROPERTY INITIALIZER:
+// `private readonly Func _fn = fn ?? Default;`. local-binding.dl binds a name
+// inside a member; an initializer belongs to no method, so the name stays unbound.
+delegate_param_ref(e, p) :-
+ delegate_initializer_name(e, n, t),
+ type_group("client", t, gk), type_ctor("client", gk, ctor),
+ method_kind("client", ctor, "PRIMARY_CONSTRUCTOR"),
+ param_decl("client", ctor, _, n, p).
+delegate_initializer_name(e, n, t) :-
+ delegate_store("client", f, e), field_decl("client", _, t, f),
+ expr_kind("client", e, "NAME_REFERENCE"), expr_ref_unknown("client", e), expr_written_name("client", e, n).
+delegate_initializer_name(e, n, t) :-
+ delegate_store("client", pr, e), property_decl("client", _, t, pr),
+ expr_kind("client", e, "NAME_REFERENCE"), expr_ref_unknown("client", e), expr_written_name("client", e, n).
+
+// ── WHAT A PARAMETER HOLDS ──────────────────────────────────────────────────
+delegate_param_holds(p, f) :-
+ delegate_param_receives(p, a), expr_anon_decl("client", a, f), method_decl("client", _, _, _, f).
+delegate_param_holds(p, f) :-
+ delegate_param_receives(p, a), delegate_group_method("client", a, f), method_decl("client", _, _, _, f).
+delegate_param_holds(p, f) :-
+ delegate_param_receives(p, a), delegate_param_ref(a, q), q != p, delegate_param_holds(q, f).
+delegate_param_holds(p, f) :-
+ delegate_param_receives(p, a), delegate_member_ref("client", a, mem), delegate_member_holds("client", mem, f).
+delegate_param_holds_any(p) :- delegate_param_holds(p, _).
+
+// A parameter STORED in a member: `_a = a`, `_fn = fn ?? Zero`.
+delegate_member_holds("client", mem, f) :-
+ delegate_store("client", mem, e), delegate_param_ref(e, p), delegate_param_holds(p, f).
+
+// ── A CALL THROUGH THE PARAMETER ────────────────────────────────────────────
+// `act(1)`, `handler(x)`, and the explicit `handler.Invoke(x)` / `a?.Invoke()`.
+delegate_param_call(e, p) :-
+ csite("client", e), call_receiver_kind("client", e, "NONE"),
+ expr_method_name_child("client", e, x), delegate_param_ref(x, p),
+ delegate_param_holds_any(p).
+delegate_param_call(e, p) :-
+ call_callee_name("client", e, "Invoke"),
+ call_receiver_expr("client", e, r), delegate_param_ref(r, p),
+ delegate_param_holds_any(p).
+
+delegate_param_node(p, id) :-
+ delegate_param_call(_, p),
+ param_decl("client", m, _, pn, p), method_decl("client", mn, _, _, m),
+ method_owner("client", t, m), type_decl("client", _, qn, _, _, t),
+ id = cat("generated:", cat(qn, cat("#", cat(mn, cat(".", cat(pn, ".Invoke")))))).
+
+method_dispatch_candidate(base, f, "value") :-
+ delegate_param_node(p, base), delegate_param_holds(p, f).
diff --git a/graph/csharp/engine/resolution/delegates.dl b/graph/csharp/engine/resolution/delegates.dl
index 13eb9773..8f6d31ae 100644
--- a/graph/csharp/engine/resolution/delegates.dl
+++ b/graph/csharp/engine/resolution/delegates.dl
@@ -45,9 +45,24 @@
// right-hand side (`f = Make()`) stores the call's return value, which is not
// followed.
//
+// A FIELD-LIKE EVENT IS FOLLOWED AS A FIELD IS (#1449). `public event EventHandler?
+// Ticked;` is a delegate field the compiler wraps in add and remove accessors: `+=`
+// adds the handler to the same invocation list, and `Ticked?.Invoke(...)` inside the
+// class calls through it. So an event member is one more member kind here, with its
+// own Invoke node. An event with explicit accessors cannot be invoked by name (CS0079),
+// so treating every event this way adds no call that does not compile; its `+=` keeps
+// the event_subscribe accessor edge (properties.dl) beside what is stored.
+//
+// A METHOD GROUP HANDED OVER (#1550). A method group that is not stored in a member
+// this file follows -- an argument (`new Timer(Sweep, ...)`, `Run(Report)`,
+// `token.Register(OnStop)`) or the value assigned to a member the engine does not know
+// (`AppDomain.CurrentDomain.ProcessExit += OnExit`) -- is handed to code that may
+// invoke it, and nothing else names it. method_group_handoff below records it, and
+// call_chain.dl emits it as a `callback_registered` edge from the enclosing method.
+//
// NOT HANDLED, and left exactly as before: a delegate that reaches the field from a
-// parameter or a local (`this.f = f`), a local or parameter of delegate type invoked
-// directly, and an event (`+=` on an event is an accessor call, accessor-edges).
+// parameter or a local (`this.f = f`), and a local or parameter of delegate type
+// invoked directly.
// ============================================================================
// ── THE MEMBER AN EXPRESSION DENOTES ────────────────────────────────────────
@@ -59,6 +74,8 @@ delegate_member_ref(prov, e, mem) :-
expr_kind(prov, e, "NAME_REFERENCE"), ref_denotes(prov, e, "FIELD", mem).
delegate_member_ref(prov, e, mem) :-
expr_kind(prov, e, "NAME_REFERENCE"), ref_denotes(prov, e, "PROPERTY", mem).
+delegate_member_ref(prov, e, mem) :-
+ expr_kind(prov, e, "NAME_REFERENCE"), ref_denotes(prov, e, "EVENT", mem).
delegate_member_ref(prov, e, mem) :-
expr_kind(prov, e, "MEMBER_ACCESS"),
expr_qualifier_child(prov, e, q), expr_member_name_child(prov, e, nm),
@@ -71,6 +88,7 @@ delegate_qualifier_type(prov, q, gk) :- expr_type(prov, q, gk).
delegate_qualifier_type(prov, q, gk) :- ref_names_type(prov, q, gk).
delegate_member_kind("field").
delegate_member_kind("property").
+delegate_member_kind("event").
// The null-forgiving `!` and parentheses around a callee or a receiver name the
// same member: `named!(url)`, `(f)(url)`.
@@ -130,15 +148,18 @@ delegate_value_method(prov, e, m) :- delegate_stored_expr(prov, e), expr_anon_de
// the plain positions METHOD_GROUP, but not a name inside `?:` or `??`, so the name
// is looked up here rather than read off that mark. Every overload of the name is a
// candidate: which one the conversion picks needs the delegate's parameter types.
-delegate_value_method(prov, e, m) :-
- delegate_stored_expr(prov, e),
+delegate_value_method(prov, e, m) :- delegate_group_method(prov, e, m).
+delegate_group_demand(prov, e) :- delegate_stored_expr(prov, e).
+delegate_group_demand(prov, e) :- delegate_handed_expr(prov, e).
+delegate_group_method(prov, e, m) :-
+ delegate_group_demand(prov, e),
expr_kind(prov, e, "NAME_REFERENCE"), expr_ref_unknown(prov, e),
expr_written_name(prov, e, n), !local_binds(prov, e, _),
expr_ultimate_type_group(prov, e, gk),
member_lookup(prov, gk, n, "method", m).
// `this.M` and `Type.M`.
-delegate_value_method(prov, e, m) :-
- delegate_stored_expr(prov, e),
+delegate_group_method(prov, e, m) :-
+ delegate_group_demand(prov, e),
expr_kind(prov, e, "MEMBER_ACCESS"),
expr_qualifier_child(prov, e, q), expr_member_name_child(prov, e, nm),
expr_written_name(prov, nm, n),
@@ -160,6 +181,36 @@ delegate_member_holds(prov, mem, m) :-
delegate_member_holds_any(prov, mem) :- delegate_member_holds(prov, mem, _).
+// ── A METHOD GROUP HANDED OVER, NOT STORED IN A FOLLOWED MEMBER (#1550) ────
+// An argument, and the branches inside it (`?:`, `??`, a cast, parentheses,
+// `new Action(M)`). A lambda argument is left out: it is its own method, already
+// defined inside the caller, and an edge to it would say the same thing twice.
+delegate_arg_expr(prov, e) :- expr_child(prov, _, "ARGUMENT", _, e).
+delegate_arg_expr(prov, c) :- delegate_arg_expr(prov, p), delegate_value_branch(prov, p, c).
+// The value of `=`, `+=` or `??=` whose target is not a member this file follows:
+// an event or a property of an unstaged type, `AppDomain.CurrentDomain.ProcessExit
+// += OnExit`. A target that IS followed stores the method in its envelope instead.
+delegate_handed_value(prov, v) :-
+ expr_kind(prov, a, "ASSIGNMENT"), expr_operator(prov, a, "="),
+ expr_child(prov, a, "ASSIGNMENT_TARGET", _, t), !delegate_member_ref(prov, t, _),
+ expr_child(prov, a, "ASSIGNMENT_VALUE", _, v).
+delegate_handed_value(prov, v) :-
+ expr_kind(prov, a, "COMPOUND_ASSIGNMENT"), expr_operator(prov, a, op), delegate_storing_op(op),
+ expr_child(prov, a, "ASSIGNMENT_TARGET", _, t), !delegate_member_ref(prov, t, _),
+ expr_child(prov, a, "ASSIGNMENT_VALUE", _, v).
+delegate_handed_value(prov, c) :- delegate_handed_value(prov, p), delegate_value_branch(prov, p, c).
+delegate_handed_expr(prov, e) :- delegate_arg_expr(prov, e).
+delegate_handed_expr(prov, e) :- delegate_handed_value(prov, e).
+
+// method_group_handoff(Prov, Expr, Method): Expr, in one of those positions, converts
+// the method group naming Method. The same lookup a stored value gets, so every
+// overload of the name is a candidate: which one the conversion picks needs the
+// delegate's parameter types, and each is exported as a hand-off, never as a call.
+method_group_handoff(prov, e, m) :-
+ delegate_handed_expr(prov, e), !expr_anon_decl(prov, e, _),
+ delegate_group_method(prov, e, m),
+ method_decl(prov, _, _, _, m).
+
// ── A CALL THROUGH THE MEMBER ───────────────────────────────────────────────
// delegate_member_call(Prov, Site, Member).
//
@@ -192,6 +243,8 @@ delegate_member_owner(prov, f, t) :- field_decl(prov, _, t, f).
delegate_member_owner(prov, p, t) :- property_decl(prov, _, t, p).
delegate_member_name(prov, f, n) :- field_decl(prov, n, _, f).
delegate_member_name(prov, p, n) :- property_decl(prov, n, _, p).
+delegate_member_owner(prov, ev, t) :- event_decl(prov, _, t, ev).
+delegate_member_name(prov, ev, n) :- event_decl(prov, n, _, ev).
// delegate_invoke_node(Member, NodeId) -- one per member some call goes through.
delegate_invoke_node(mem, id) :-
diff --git a/graph/csharp/engine/resolution/extensions.dl b/graph/csharp/engine/resolution/extensions.dl
index cfebbc73..5a09820c 100644
--- a/graph/csharp/engine/resolution/extensions.dl
+++ b/graph/csharp/engine/resolution/extensions.dl
@@ -118,6 +118,25 @@ call_recv_type_name(prov, e, tn) :-
call_recv_type_name(prov, e, tn) :-
call_receiver_expr(prov, e, r),
ref_denotes(prov, r, "LAMBDA_PARAMETER", p), param_type_name(prov, p, tn, _).
+// AN IMPLICITLY TYPED LAMBDA PARAMETER takes its type from the delegate it is
+// converted to (lambda-parameters.dl): `Configure(s => s.AddRules())` with
+// `Configure(Action c)` makes `s` an IServiceCollection. Where
+// that type is in source, param_type already says so; where it is unstaged, the
+// NAME is all there is, and without it an extension written for
+// `this IServiceCollection` cannot match the lambda that calls it.
+call_recv_type_name(prov, e, tn) :-
+ call_receiver_expr(prov, e, r),
+ ref_denotes(prov, r, "LAMBDA_PARAMETER", p),
+ lambda_param_arg_ref(prov, p, ar), type_ref_name(prov, ar, tn), tn != "".
+// AN IMPLICITLY TYPED LOCAL made by `new T(..)` holds a T, resolved or not:
+// `var services = new ServiceCollection(); services.AddRules();`. The name is the
+// one written at the creation (external-types.dl labels the boundary from the same
+// fact); without it the extension rungs have nothing to compare.
+call_recv_type_name(prov, e, tn) :-
+ call_receiver_expr(prov, e, r),
+ ref_denotes(prov, r, "LOCAL_VARIABLE", v), var_is_implicit(prov, v),
+ var_value_initializer(prov, v, init), expr_kind(prov, init, "OBJECT_CREATION"),
+ call_callee_name(prov, init, tn), tn != "".
// A receiver that is itself a call: the callee's declared return type name. The ROOT
// reference's name where there is one (`List`, not `List- `), which is how every
// other receiver is named; the written text only where no reference was recorded.
@@ -246,6 +265,42 @@ extension_match(prov, e, m) :-
call_recv_type_name(prov, e, tn),
extension_recv_name(prov, m, tn).
+// (5) THROUGH THE RECEIVER'S UNSTAGED HERITAGE. An in-source class that derives from
+// or implements an unstaged type the `this` parameter names: `class AppServices :
+// IServiceCollection` against `this IServiceCollection`. The heritage entry is
+// written with the same name the parameter is, so the match is as firm as (3). (No
+// test that the `this` type is itself unresolved: that negation would sit inside the
+// typing recursion, and a heritage entry that failed to resolve names no staged type.)
+extension_match(prov, e, m) :-
+ call_callee_name(prov, e, n),
+ expr_ultimate_module(prov, e, mod),
+ extension_in_scope(prov, mod, m),
+ method_name(prov, m, n),
+ call_argc(prov, e, ac), method_accepts_extension_argc(prov, m, ac),
+ call_recv_type(prov, e, rgk),
+ type_self_or_ancestor(prov, rgk, agk),
+ type_base_unresolved(prov, agk, _, tn, _),
+ extension_recv_name(prov, m, tn).
+
+// (6) A LIBRARY-TYPED RECEIVER (#1535). With --library, `Kit.Run()` returns the
+// staged `Report`, and `Describe(this Report r)` has its `this` type resolved to that
+// same library group. The nominal clause (1) walks type_self_or_ancestor, which is
+// keyed on the CLIENT's groups and has no row for a library group, not even the
+// reflexive one, so the extension matched only without the library and staging it
+// made the answer worse. The library's own ancestors are followed the same way.
+extension_lib_self_or_ancestor(gk, gk) :- type_group("lib", _, gk).
+extension_lib_self_or_ancestor(gk, agk) :- type_ancestor("lib", gk, agk).
+extension_match(prov, e, m) :-
+ call_callee_name(prov, e, n),
+ expr_ultimate_module(prov, e, mod),
+ extension_in_scope(prov, mod, m),
+ method_name(prov, m, n),
+ call_argc(prov, e, ac), method_accepts_extension_argc(prov, m, ac),
+ call_recv_type(prov, e, rgk),
+ type_group("lib", _, rgk),
+ extension_recv_type(prov, m, tgk),
+ extension_lib_self_or_ancestor(rgk, tgk).
+
// THE GATE. An extension method is considered only where instance lookup found
// nothing applicable, which is the language's rule and not an optimisation: a type
// with its own `Select` must bind to its own, and offering both makes every LINQ
@@ -253,6 +308,34 @@ extension_match(prov, e, m) :-
extension_candidate(prov, e, m) :-
extension_match(prov, e, m),
call_instance_lookup_failed(prov, e).
+// The candidates of the matching clauses (1)-(6), without rung (7) below: what an
+// external label on an unstaged receiver is withheld for (external-types.dl).
+extension_candidate_matched(prov, e) :- extension_match(prov, e, _), call_instance_lookup_failed(prov, e).
+
+// (7) BOTH SIDES UNSTAGED, UNDER DIFFERENT NAMES (#1472): `ServiceCollection services;
+// services.AddWidgetRules()` against `AddWidgetRules(this IServiceCollection s)`. The
+// receiver's type is unstaged, so the engine has no ancestors for it, and the `this`
+// type is unstaged too, so neither nominal nor by-name matching can say whether one
+// implements the other. It is the documented DI registration shape, and dropping it
+// hid the caller from `impact`. So it is a candidate, but never a firm one: the site
+// keeps its external label beside it and reads as one of a set. Taken only where no
+// other extension matched the site, so it never widens an answer (3) already gave.
+// An in-source `this` type is not this rung: an unstaged type cannot derive from it.
+extension_loose_match(prov, e, m) :-
+ call_callee_name(prov, e, n),
+ expr_ultimate_module(prov, e, mod),
+ extension_in_scope(prov, mod, m),
+ method_name(prov, m, n),
+ call_argc(prov, e, ac), method_accepts_extension_argc(prov, m, ac),
+ call_recv_type_name(prov, e, tn), !call_recv_type(prov, e, _),
+ external_type_name(prov, mod, tn, _),
+ !extension_recv_type(prov, m, _),
+ !extension_recv_is_generic(prov, m), !extension_recv_is_object(prov, m),
+ extension_recv_name(prov, m, xn), xn != tn.
+extension_candidate(prov, e, m) :-
+ extension_loose_match(prov, e, m),
+ call_instance_lookup_failed(prov, e),
+ !extension_match(prov, e, _).
// A candidate the engine has real evidence for, as opposed to one it reached by
// assuming an unanswerable question was answered "nothing". Only a FIRM candidate
@@ -297,6 +380,7 @@ extension_is_guess(prov, e, m) :-
extension_match(prov, e, m),
extension_recv_is_generic(prov, m),
!call_recv_type(prov, e, _).
+extension_is_guess(prov, e, m) :- extension_loose_match(prov, e, m), !extension_match(prov, e, _).
// An extension method is STATIC and is never virtually dispatched: the receiver is
// an ordinary argument, so the target is exact and needs no fan.
diff --git a/graph/csharp/engine/resolution/external-types.dl b/graph/csharp/engine/resolution/external-types.dl
index 837f10ef..966db7a1 100644
--- a/graph/csharp/engine/resolution/external-types.dl
+++ b/graph/csharp/engine/resolution/external-types.dl
@@ -87,6 +87,25 @@ expr_external_type_name(prov, e, n) :-
property_decl(prov, _, t, p), type_module(prov, t, mod),
external_type_name(prov, mod, n, _).
+// A QUALIFIED property or field read: `result.Errors.Add(x)` where `Errors` is declared
+// `List`. The rules above take a bare name the parser bound; a member access
+// is bound by member lookup on its qualifier, and its declared type names the receiver
+// the same way. Without it the call was left unresolved and read as a call BY NAME to
+// every project method called Add.
+expr_external_type_name(prov, e, n) :-
+ property_access(prov, e, p, _), property_type_name(prov, p, n, _),
+ property_decl(prov, _, t, p), type_module(prov, t, mod),
+ external_type_name(prov, mod, n, _).
+expr_external_type_name(prov, e, n) :-
+ expr_qualified_field(prov, e, f), field_type_name(prov, f, n, _),
+ field_decl(prov, _, t, f), type_module(prov, t, mod),
+ external_type_name(prov, mod, n, _).
+expr_qualified_field(prov, e, f) :-
+ expr_kind(prov, e, "MEMBER_ACCESS"), expr_role(prov, e, "RECEIVER"),
+ expr_qualifier_child(prov, e, q), expr_member_name_child(prov, e, nm),
+ expr_written_name(prov, nm, n), expr_type(prov, q, gk),
+ member_lookup(prov, gk, n, "field", f).
+
// A METHOD WHOSE DECLARED RETURN TYPE IS UNSTAGED: `List
- Items()`. Named by the
// ROOT reference (`List`), not by the written text (`List
- `), so the label is the
// same whatever the type argument, as it is for a parameter or a field of that type.
@@ -244,6 +263,21 @@ expr_predefined_recv_name(prov, e, n) :-
ref_denotes(prov, e, "FIELD", f), field_type_name(prov, f, n, _), predefined_boundary_alias(n).
expr_predefined_recv_name(prov, e, n) :-
ref_denotes(prov, e, "PROPERTY", p), property_type_name(prov, p, n, _), predefined_boundary_alias(n).
+// A KEYWORD type as a static receiver: `string.Format(..)`, `int.Parse(..)`. A keyword
+// is never a variable, so the name alone says the call is on the framework type.
+expr_predefined_recv_name(prov, e, n) :-
+ expr_kind(prov, e, "NAME_REFERENCE"), expr_role(prov, e, "RECEIVER"),
+ expr_written_name(prov, e, n), predefined_boundary_alias(n).
+// The parser writes a keyword receiver with no name at all (every identifier it does
+// name), so a nameless receiver name is a keyword type; which one is not recorded.
+expr_predefined_recv_name(prov, e, "predefined") :-
+ expr_kind(prov, e, "NAME_REFERENCE"), expr_role(prov, e, "RECEIVER"),
+ !expr_written_name(prov, e, _), !ref_denotes(prov, e, _, _).
+// and a qualified property or field declared with a keyword type: `ctx.Name.Trim()`
+expr_predefined_recv_name(prov, e, n) :-
+ property_access(prov, e, p, _), property_type_name(prov, p, n, _), predefined_boundary_alias(n).
+expr_predefined_recv_name(prov, e, n) :-
+ expr_qualified_field(prov, e, f), field_type_name(prov, f, n, _), predefined_boundary_alias(n).
// A LITERAL receiver: `"text".Trim()`.
// A RECEIVER THAT IS ITSELF A CALL, returning a predefined type. `sb.ToString()`
// on a staged StringBuilder resolves, and its declared return type is `string`,
@@ -271,7 +305,9 @@ external_target(prov, e, label) :-
external_type_name(prov, mod, tn, _),
call_callee_name(prov, e, m),
!call_recv_type(prov, e, _),
- !extension_candidate(prov, e, _),
+ // A candidate of the loose rung (7) in extensions.dl keeps the label, so the
+ // site reads as one of a set rather than committing to the guess.
+ !extension_candidate_matched(prov, e),
label = cat("external:", cat(tn, cat(".", m))).
// ── AN OPERATOR ON AN UNSTAGED OPERAND TYPE ─────────────────────────────────
@@ -324,12 +360,18 @@ external_target(prov, e, label) :-
// the answer is uncertain. So an external target is added ALONGSIDE the local one.
// The local edge is not removed -- it may well be right -- and the site becomes
// multi_inferred, which is the truthful claim: it is one of these two.
+//
+// ONLY AN ENTRY LOOKUP CAN REACH (#1431). `class Order : OrderBase, IComparable`
+// has an unstaged entry, but it is an interface after the base class, and lookup on
+// an Order receiver never considers an interface's members: `external:IComparable.Total`
+// beside `Order.Total` named a target that cannot exist. type_member_base_unresolved
+// keeps position 0 of a class (a base class or an interface, undecidable by name) and
+// every entry of an interface.
external_target(prov, e, label) :-
call_recv_type(prov, e, gk),
- type_hierarchy_incomplete(prov, gk),
+ type_member_base_unresolved(prov, gk, bn),
call_callee_name(prov, e, n),
member_method(prov, gk, n, _),
- type_base_unresolved(prov, gk, _, bn, _),
label = cat("external:", cat(bn, cat(".", n))).
// The same for an UNQUALIFIED call, whose receiver is the implicit `this`: the
@@ -337,10 +379,27 @@ external_target(prov, e, label) :-
external_target(prov, e, label) :-
call_is_unqualified(prov, e),
expr_ultimate_type_group(prov, e, gk),
- type_hierarchy_incomplete(prov, gk),
+ type_member_base_unresolved(prov, gk, bn),
call_callee_name(prov, e, n),
member_method(prov, gk, n, _),
- type_base_unresolved(prov, gk, _, bn, _),
+ label = cat("external:", cat(bn, cat(".", n))).
+
+// ── `base.M()` THROUGH AN UNSTAGED BASE CLASS (#1450) ───────────────────────
+// `base.OnModelCreating(mb)` in a class deriving from an unstaged DbContext. The
+// receiver is typed only through a RESOLVED base (callee-resolution.dl), so the site
+// had neither a target nor a label, was ambiguous_unknown, and `impact` then offered
+// it by name as a caller of every sibling context's OnModelCreating, which it can
+// never call: `base.` binds to the base class's member, non-virtually. It is named
+// here as the unstaged base's member. Position 0 of a class is the only place a base
+// class can be written; a class whose declared base resolved is left to the staged
+// lookup, and one with no heritage has nothing to name.
+external_target(prov, e, label) :-
+ call_is_base(prov, e),
+ expr_ultimate_type_group(prov, e, gk),
+ !type_has_declared_base(prov, gk),
+ type_base_unresolved(prov, gk, "0", bn, _),
+ type_group(prov, t, gk), type_is_class(prov, t),
+ call_callee_name(prov, e, n),
label = cat("external:", cat(bn, cat(".", n))).
// ── A MEMBER THE STAGED LIBRARY DOES NOT CARRY IS STILL NAMED ───────────────
@@ -350,11 +409,16 @@ external_target(prov, e, label) :-
// where the stub carries the one-argument overload and the source calls the
// two-argument one, and `string.Any`, an extension method on no version of the
// type. The type IS known, so the site is still named.
+//
+// NOT WHERE AN IN-SOURCE EXTENSION IS THE ANSWER (#1535). `Kit.Run().Describe()` with
+// `Describe(this Report r)` in the project is that extension, and labelling it a
+// member of the library's Report hid the call from the method it reaches.
external_target(prov, e, label) :-
call_recv_type(prov, e, gk),
type_group("lib", _, gk),
call_callee_name(prov, e, n),
!member_method(prov, gk, n, _),
+ !extension_candidate_firm(prov, e, _),
type_decl("lib", bn, _, _, _, t), type_group("lib", t, gk),
label = cat("external:", cat(bn, cat(".", n))).
@@ -363,6 +427,7 @@ external_target(prov, e, label) :-
type_group("lib", _, gk),
call_callee_name(prov, e, n),
call_arity_no_member(prov, e),
+ !extension_candidate_firm(prov, e, _),
type_decl("lib", bn, _, _, _, t), type_group("lib", t, gk),
label = cat("external:", cat(bn, cat(".", n))).
diff --git a/graph/csharp/engine/resolution/lambda-parameters.dl b/graph/csharp/engine/resolution/lambda-parameters.dl
index ecfc8ce5..f13187a1 100644
--- a/graph/csharp/engine/resolution/lambda-parameters.dl
+++ b/graph/csharp/engine/resolution/lambda-parameters.dl
@@ -51,6 +51,60 @@ lambda_target_ref(prov, c, tr) :- expr_cast_ref(prov, e, tr), expr_child(prov, e
// Through `?:`, `??`, parentheses and `!`.
lambda_target_ref(prov, c, tr) :- lambda_target_ref(prov, p, tr), delegate_value_branch(prov, p, c).
+// ── A LAMBDA PASSED AS AN ARGUMENT, TO A CALL THAT HAS ONE CANDIDATE ────────
+// `r.Setup(o => o.Reset())` with `Registry Setup(Action configure)`:
+// the lambda is converted to the parameter it is passed to, so `o` is a
+// WidgetOptions (#1442). The header's caveat is about OVERLOADS, where which
+// parameter binds depends on the lambda; so this takes the parameter only where
+// the callee has no overload to choose between: no other method of that name in
+// its declaring type, and the parameter is not a `params` one. That is decided
+// from the declarations alone, so it cannot feed back into the overload choice it
+// would otherwise depend on.
+//
+// The ordinal is the argument's among the ARGUMENT children, with no named
+// argument at the site (a named one may bind anywhere). An extension method called
+// on a receiver (`services.Configure(o => ...)`) takes its receiver as parameter 0,
+// so the argument at ordinal k fills parameter k + 1.
+lambda_target_ref(prov, a, tr) :-
+ lambda_arg_at(prov, e, k, a),
+ expr_target_any(prov, e, m), lambda_callee_single(prov, m), !method_is_extension(prov, m),
+ lambda_callee_param_ref(prov, m, k, tr).
+lambda_target_ref(prov, a, tr) :-
+ lambda_arg_at(prov, e, k, a),
+ expr_target_any(prov, e, m), lambda_callee_single(prov, m), method_is_extension(prov, m),
+ call_receiver_expr(prov, e, r), expr_type(prov, r, _),
+ lambda_callee_param_ref(prov, m, k + 1, tr).
+
+// A lambda ELEMENT of a collection initializer is the argument of the Add the element
+// runs (call-edge-generation/implicit-calls.dl): `new Rules { v => v.RuleFor(x => x.Name) }`
+// passes the lambda to `Add(Func creator)`, so `v` is that parameter's first
+// type argument. The same single-candidate guard as an argument.
+lambda_target_ref(prov, el, tr) :-
+ expr_kind(prov, oc, "OBJECT_CREATION"), expr_type(prov, oc, gk),
+ expr_child(prov, oc, "INITIALIZER_VALUE", _, i), expr_kind(prov, i, "INITIALIZER"),
+ expr_parent(prov, el, i, _, _), expr_anon_decl(prov, el, _),
+ member_method(prov, gk, "Add", m), lambda_callee_single(prov, m), method_paramc(prov, m, "1"),
+ lambda_callee_param_ref(prov, m, 0, tr).
+
+// A lambda among a call's ARGUMENT children, by ordinal, at a site with no named argument.
+lambda_arg_at(prov, e, k, a) :-
+ expr_argument(prov, e, pos, a), expr_anon_decl(prov, a, _),
+ !call_has_named_args(prov, e),
+ p = to_number(pos),
+ k = count : { expr_argument(prov, e, q, _), to_number(q) < p }.
+
+lambda_callee_single(prov, m) :-
+ method_in_type(prov, gk, m), method_name(prov, m, n),
+ !lambda_callee_overloaded(prov, gk, n).
+lambda_callee_overloaded(prov, gk, n) :-
+ method_in_type(prov, gk, m1), method_name(prov, m1, n),
+ method_in_type(prov, gk, m2), method_name(prov, m2, n), m1 != m2.
+
+// The root type reference of the callee's parameter at an ordinal, not a `params` one.
+lambda_callee_param_ref(prov, m, k, tr) :-
+ param_decl(prov, m, pos, _, p), k = to_number(pos),
+ !param_is_params(prov, p), param_type_ref(prov, p, tr).
+
// A property's declared type, as a reference. property_type reads the NAME, which
// drops the arguments `Func` is made of.
lambda_property_type_ref(prov, p, tr) :-
diff --git a/graph/csharp/engine/resolution/local-binding.dl b/graph/csharp/engine/resolution/local-binding.dl
index f2ec9440..e7bc391f 100644
--- a/graph/csharp/engine/resolution/local-binding.dl
+++ b/graph/csharp/engine/resolution/local-binding.dl
@@ -220,7 +220,8 @@ ref_unbound(prov, e, n) :-
!ref_names_type(prov, e, _).
// A reference that names a TYPE rather than a value: the receiver of a static call.
-// Resolved through type_name_resolves in the file's scope.
+// Resolved through type_name_resolves in the file's scope, at the arity written on
+// it: `Cache.Get()` names Cache`1, not a non-generic Cache.
//
// NOT WHERE THE NAME ALSO BINDS TO A VALUE. C# allows a member and a type to share a
// name (the language's own "Color Color" rule) and prefers the MEMBER in an
@@ -235,10 +236,10 @@ ref_binds_value(prov, e) :- ref_denotes(prov, e, _, _).
ref_binds_value(prov, e) :- local_binds(prov, e, _).
ref_names_type(prov, e, gk) :-
- expr_written_name(prov, e, n),
+ expr_written_name(prov, e, n), expr_type_argc(prov, e, ar),
expr_ultimate_module(prov, e, mod),
!ref_binds_value(prov, e),
- type_name_resolves(prov, mod, n, "0", gk).
+ type_name_resolves(prov, mod, n, ar, gk).
// ── IMPLICIT `this` ─────────────────────────────────────────────────────────
// An unqualified instance member reference has an implicit `this` receiver whose
diff --git a/graph/csharp/engine/resolution/member-lookup.dl b/graph/csharp/engine/resolution/member-lookup.dl
index 88f67303..5193e110 100644
--- a/graph/csharp/engine/resolution/member-lookup.dl
+++ b/graph/csharp/engine/resolution/member-lookup.dl
@@ -43,7 +43,8 @@ member_cand(prov, gk, n, kind, mem, d) :-
// The chain may cross into a staged library.
member_cand("client", gk, n, kind, mem, d) :-
type_base_chain("client", gk, bgk, d),
- type_member_name("lib", bgk, n, kind, mem).
+ type_member_name("lib", bgk, n, kind, mem),
+ !lib_member_inaccessible(mem).
// AND THE RECEIVER'S OWN TYPE MAY BE THE LIBRARY'S. `void M(Greeter g) => g.Hello()`
// asks member_lookup("client", , "Hello"), and the members of that group
@@ -52,7 +53,8 @@ member_cand("client", gk, n, kind, mem, d) :-
// client simply holding one. Without it a call on a library-typed receiver resolves
// to nothing even with the library staged and its name resolved.
member_cand("client", gk, n, kind, mem, 0) :-
- type_member_name("lib", gk, n, kind, mem).
+ type_member_name("lib", gk, n, kind, mem),
+ !lib_member_inaccessible(mem).
// AND THE LIBRARY TYPE'S OWN BASE CHAIN. The clause above is distance 0 only, so
// a client site saw a staged type's own members and none it inherits -- and the
@@ -64,7 +66,28 @@ member_cand("client", gk, n, kind, mem, 0) :-
// invisible. That is most of `object`: ToString, Equals, GetHashCode, GetType.
member_cand("client", gk, n, kind, mem, d) :-
type_base_chain("lib", gk, bgk, d),
- type_member_name("lib", bgk, n, kind, mem).
+ type_member_name("lib", bgk, n, kind, mem),
+ !lib_member_inaccessible(mem).
+
+// ── A LIBRARY MEMBER THE CLIENT CANNOT ACCESS IS NOT A MEMBER TO IT ─────────
+// The three clauses above cross from client code into a staged library, and C#
+// member lookup removes every member that is not accessible at the site before it
+// picks the nearest one (spec 12.5). A `private`, `internal` or `private protected`
+// member of another assembly is never accessible from the client, so it neither
+// resolves a client access nor hides anything: inside `Gadget : WidgetBase`, the
+// unqualified `Limits` in `Limits.Allow(n)` is the client's own class `Limits`, not
+// the library base's `internal List Limits` (#1538). Without it the inherited
+// internal member won the simple-name lookup and the call went to a library getter.
+// `protected` and `protected internal` stay: a derived client type may use them.
+// InternalsVisibleTo is not modelled; an assembly that grants it is rare, and the
+// cost there is a missed edge, not a wrong one.
+lib_member_inaccessible(m) :- lib_cs_method(_, _, _, _, _, _, a, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, m), cs_access_outside_assembly_denied(a).
+lib_member_inaccessible(p) :- lib_cs_property(_, _, _, _, _, a, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, p), cs_access_outside_assembly_denied(a).
+lib_member_inaccessible(f) :- lib_cs_field(_, _, _, _, _, a, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, f), cs_access_outside_assembly_denied(a).
+lib_member_inaccessible(ev) :- lib_cs_event(_, _, _, _, a, _, _, _, _, _, _, _, _, _, _, _, ev), cs_access_outside_assembly_denied(a).
+cs_access_outside_assembly_denied("PRIVATE").
+cs_access_outside_assembly_denied("INTERNAL").
+cs_access_outside_assembly_denied("PRIVATE_PROTECTED").
// ── AN EXPLICIT INTERFACE IMPLEMENTATION IS EXCLUDED ────────────────────────
// It is not a member of the declaring type. Removed from the candidate set rather
@@ -90,6 +113,36 @@ member_lookup(prov, gk, n, kind, mem) :-
member_best_rank(prov, gk, n, d),
!member_is_explicit_impl(prov, mem).
+// ── AN OVERRIDE DOES NOT HIDE ITS BASE'S OVERLOADS ──────────────────────────
+// For a METHOD the nearest declaration is not the whole answer. C# member lookup
+// removes an override from the candidate set (it is the virtual member it
+// overrides, not a new one) and a derived method hides only a base method of the
+// same signature. So a type that overrides one `ValidateAsync` overload still has
+// its base's other `ValidateAsync` overloads, and `v.ValidateAsync(person)` on it
+// binds to the base's `ValidateAsync(T)`. The nearest-rank rule above stopped at the
+// override and the call resolved to it, or to nothing.
+//
+// Kept to the case where every nearer declaration of the name is an override: a
+// derived type that declares a NEW method of the name makes the base's applicable
+// only when none of its own are, which needs the argument types; that case keeps
+// the nearest answer. A base candidate is still hidden by a nearer one with the
+// same signature text, which is the override of that very member.
+member_method_newdecl(prov, gk, n, d) :-
+ member_cand(prov, gk, n, "method", m, d), !member_is_explicit_impl(prov, m), !method_is_override(_, m).
+member_method_newdecl_before(prov, gk, n, d) :-
+ member_cand(prov, gk, n, "method", _, d), member_method_newdecl(prov, gk, n, d2), d2 < d.
+member_method_sig_hidden(prov, gk, n, m, d) :-
+ member_cand(prov, gk, n, "method", m, d), method_signature(_, m, s),
+ member_cand(prov, gk, n, "method", m2, d2), d2 < d, method_signature(_, m2, s),
+ !member_is_explicit_impl(prov, m2).
+member_lookup(prov, gk, n, "method", mem) :-
+ member_cand(prov, gk, n, "method", mem, d),
+ member_best_rank(prov, gk, n, d0), d > d0,
+ member_cand(prov, gk, n, "method", _, d0),
+ !member_is_explicit_impl(prov, mem),
+ !member_method_newdecl_before(prov, gk, n, d),
+ !member_method_sig_hidden(prov, gk, n, mem, d).
+
// ── INTERFACE MEMBERS ───────────────────────────────────────────────────────
// A call through an interface-typed receiver looks up in the interface and in every
// interface it extends. No distance ordering: interface inheritance is a lattice and
diff --git a/graph/csharp/engine/resolution/properties.dl b/graph/csharp/engine/resolution/properties.dl
index 2c760ad5..2ff3aa0b 100644
--- a/graph/csharp/engine/resolution/properties.dl
+++ b/graph/csharp/engine/resolution/properties.dl
@@ -101,6 +101,31 @@ property_access(prov, acc, p, gk) :-
// implicit `this`. The parser binds these itself (referencedEntityKind PROPERTY),
// so the engine takes its answer rather than re-deriving it.
property_access_unqualified(prov, e, p) :- ref_denotes(prov, e, "PROPERTY", p).
+// A NULL-CONDITIONAL READ: `ctx?.Parser?.Row`. The parser writes `a?.B` as a member
+// access whose MEMBER_NAME child is itself a nameless member access holding the name
+// `B` as its qualifier, so the rules above, which read a written member name, never
+// saw it, and every property read in a `?.` chain had no edge. The property is looked
+// up on the qualifier's type and the site is the name; the access takes its type.
+cond_binding(prov, acc, q, nm, n) :-
+ expr_kind(prov, acc, "MEMBER_ACCESS"), expr_qualifier_child(prov, acc, q),
+ expr_member_name_child(prov, acc, b), expr_kind(prov, b, "MEMBER_ACCESS"), !expr_written_name(prov, b, _),
+ expr_qualifier_child(prov, b, nm), expr_kind(prov, nm, "NAME_REFERENCE"), expr_written_name(prov, nm, n).
+property_access(prov, nm, p, gk) :-
+ cond_binding(prov, _, q, nm, n), expr_type(prov, q, gk), member_lookup(prov, gk, n, "property", p).
+expr_type(prov, acc, t) :- cond_binding(prov, acc, _, nm, _), expr_type(prov, nm, t).
+expr_type(prov, nm, t) :- cond_binding(prov, _, _, nm, _), property_access(prov, nm, p, _), property_type(prov, p, t).
+
+// AN OBJECT INITIALIZER NAMES A MEMBER OF THE OBJECT IT CREATES. In `new Cfg { Size = 4 }`
+// the target `Size` is looked up on Cfg, not in the enclosing method's scope, so neither
+// rule above saw it and the set accessor every initializer runs had no edge (a third to
+// a half of the property writes a configuration-heavy codebase makes).
+property_access(prov, e, p, gk) :-
+ expr_role(prov, e, "INITIALIZER_TARGET"), expr_kind(prov, e, "NAME_REFERENCE"),
+ expr_written_name(prov, e, n),
+ expr_parent(prov, e, asg, _, _), expr_kind(prov, asg, "ASSIGNMENT"),
+ expr_parent(prov, asg, init, _, _), expr_kind(prov, init, "INITIALIZER"),
+ expr_parent(prov, init, oc, _, _), expr_type(prov, oc, gk),
+ member_lookup(prov, gk, n, "property", p).
// ── READ OR WRITE ───────────────────────────────────────────────────────────
// The edge role decides: an ASSIGNMENT_TARGET is a write, everything else is a
@@ -232,6 +257,34 @@ accessor_edge(prov, e, acc, "indexer") :- indexer_write_target(prov, e, acc).
accessor_edge(prov, e, acc, "event_subscribe") :- event_add_target(prov, e, acc).
accessor_edge(prov, e, acc, "event_subscribe") :- event_remove_target(prov, e, acc).
+// THE DECLARATION A SITE IS WRITTEN AGAINST, when it has no body: an interface or an
+// abstract property's accessor. The fan above names the bodies that can run; the
+// declaration is in none of them, and without its own edge an interface property has
+// no reader at all. The same edge a bodiless method gets in call_chain.dl.
+accessor_site_decl(prov, e, decl, "property_read") :-
+ property_access(prov, e, p, _), !expr_is_write_target(prov, e), property_getter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "property_read") :-
+ property_access_unqualified(prov, e, p), !expr_is_write_target(prov, e), property_getter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "property_write") :-
+ property_access(prov, e, p, _), expr_is_write_target(prov, e), property_setter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "property_write") :-
+ property_access_unqualified(prov, e, p), expr_is_write_target(prov, e), property_setter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "property_write") :-
+ property_access(prov, e, p, _), expr_is_readwrite_target(prov, e), property_setter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "indexer") :-
+ indexer_access(prov, e, p), !expr_is_write_target(prov, e), indexer_getter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "indexer") :-
+ indexer_access(prov, e, p), expr_is_write_target(prov, e), indexer_setter(prov, p, decl).
+accessor_site_decl(prov, e, decl, "event_subscribe") :- event_access(prov, e, ev), event_adder(prov, ev, decl).
+accessor_site_decl(prov, e, decl, "event_subscribe") :- event_unaccess(prov, e, ev), event_remover(prov, ev, decl).
+accessor_is_bodiless(prov, acc) :-
+ method_is_accessor(prov, acc), method_owner(prov, t, acc), type_is_interface(prov, t),
+ !interface_has_default_impl(prov, acc).
+accessor_is_bodiless(prov, acc) :- method_accessor_of(prov, acc, p, _), property_modifier(prov, p, "abstract").
+accessor_names_bodiless(prov, e, decl, kind) :-
+ accessor_site_decl(prov, e, decl, kind), accessor_is_bodiless(prov, decl),
+ !accessor_edge(prov, e, decl, kind), accessor_edge(prov, e, _, kind).
+
// How many accessors a site can reach. One is the ordinary case; more means the
// property is virtual and the receiver's type is open, which is the same fan as a
// method call and is labelled the same way.
diff --git a/graph/csharp/engine/resolution/type-hierarchy.dl b/graph/csharp/engine/resolution/type-hierarchy.dl
index a88dfd53..7c285be1 100644
--- a/graph/csharp/engine/resolution/type-hierarchy.dl
+++ b/graph/csharp/engine/resolution/type-hierarchy.dl
@@ -201,6 +201,24 @@ type_base_unresolved(prov, gk, pos, bn, ba) :-
// instead of ambiguous_unknown.
type_hierarchy_incomplete(prov, gk) :- type_base_unresolved(prov, gk, _, _, _).
+// type_member_base_unresolved(Prov, GroupKey, BaseName) -- the unresolved entries
+// MEMBER LOOKUP on a receiver of this type can reach (#1431). C# looks a member up on
+// a class or struct receiver through the class chain only, never through the
+// interfaces it implements, and a base class can only be written at position 0 of a
+// class. So an unstaged entry after a class's first, and every entry of a struct, is
+// an interface whose members the type must declare itself: a member found on the
+// type is the answer, and `external:IComparable.Total` beside it names a target no
+// lookup can reach. On an interface every entry is a base interface, and lookup does
+// go through it. An unstaged entry AT position 0 of a class is either a base class or
+// an interface, which the name alone cannot tell (an `I` prefix is a convention, not
+// proof), so it keeps the boundary. The looser type_hierarchy_incomplete is kept for
+// a MISS, which the boundary still explains when nothing is declared.
+type_member_base_unresolved(prov, gk, bn) :-
+ type_base_unresolved(prov, gk, "0", bn, _), type_group(prov, t, gk), type_is_class(prov, t).
+type_member_base_unresolved(prov, gk, bn) :-
+ type_base_unresolved(prov, gk, _, bn, _), type_group(prov, t, gk), type_is_interface(prov, t).
+type_members_incomplete(prov, gk) :- type_member_base_unresolved(prov, gk, _).
+
// ── ANCESTORS ───────────────────────────────────────────────────────────────
// type_parent -- one relation over both kinds, for reachability. The two are kept
// distinct above because dispatch needs them distinct; here they are unioned because
diff --git a/graph/csharp/engine/resolution/type-resolution.dl b/graph/csharp/engine/resolution/type-resolution.dl
index fb90ddc4..f4d1b803 100644
--- a/graph/csharp/engine/resolution/type-resolution.dl
+++ b/graph/csharp/engine/resolution/type-resolution.dl
@@ -89,6 +89,26 @@ type_name_cand(prov, mod, qn, ar, gk, 1) :-
type_decl(prov, _, qn, ar, _, t), type_group(prov, t, gk),
!type_is_top_level(prov, gk).
+// ── A QUALIFIED NAME READ FROM ITS NAMESPACE ────────────────────────────────
+// `Reflection.Info.Create(..)` written inside namespace Shop names
+// Shop.Reflection.Info: the first segment of a qualified name is a
+// simple name, looked up in the enclosing namespaces like any other. Matched only
+// in full, such a receiver stays untyped and every call through it unresolved.
+//
+// Keyed from the DECLARATION side, so nothing is enumerated per written name: each
+// type's qualified name is split at each dot into the namespace before it and the
+// dotted tail after it, and the tail is a candidate in every file whose namespace
+// chain holds that prefix. A tail with no dot is a simple name, which ranks 2-5
+// already answer.
+type_qn_tail(prov, qn, p, s) :-
+ type_decl(prov, _, qn, _, _, _),
+ i = range(1, strlen(qn)), substr(qn, i, 1) = ".",
+ p = substr(qn, 0, i), s = substr(qn, i + 1, strlen(qn) - i - 1),
+ contains(".", s).
+type_name_cand(prov, mod, s, ar, gk, 1) :-
+ type_qn_tail(prov, qn, p, s), module_ns_chain(prov, mod, p),
+ type_decl(prov, _, qn, ar, _, t), type_group(prov, t, gk).
+
// ── A CLIENT FILE NAMING A STAGED LIBRARY TYPE ──────────────────────────────
// Every clause above is SINGLE-PROVENANCE: it joins a module and a declaration
// under one `prov`, so a client module and a library namespace are never brought
@@ -211,10 +231,10 @@ type_ref_demand(prov, mod, n, ar) :-
// a type name and produced `external:s.Describe` -- an external target named after a
// local variable, which is a wrong answer rather than a missing one. The !ref_denotes
// term is what makes the guard "unbound by anyone".
-type_ref_demand(prov, mod, n, "0") :-
+type_ref_demand(prov, mod, n, ar) :-
call_receiver_expr(prov, e, r),
expr_ref_unknown(prov, r),
- expr_written_name(prov, r, n),
+ expr_written_name(prov, r, n), expr_type_argc(prov, r, ar),
!ref_denotes(prov, r, _, _),
call_module(prov, e, mod).
diff --git a/graph/csharp/engine/resolution/virtual-dispatch.dl b/graph/csharp/engine/resolution/virtual-dispatch.dl
index a429165f..d3590f56 100644
--- a/graph/csharp/engine/resolution/virtual-dispatch.dl
+++ b/graph/csharp/engine/resolution/virtual-dispatch.dl
@@ -94,17 +94,43 @@ implements_shape("client", impl, ifaceM) :-
method_paramc("client", impl, pc), method_paramc("lib", ifaceM, pc),
method_type_arity("client", impl, ar), method_type_arity("lib", ifaceM, ar).
-// The explicit form, matched by the interface NAME written on the declaration.
+// The explicit form, matched by the interface NAME written on the declaration. A
+// generic interface is written with its arguments -- `IOpt IOpt.Dep()` --
+// and the declaration's name carries none, so the name is compared up to its `<`.
+explicit_iface_written(s) :- method_explicit_interface(_, _, s).
+explicit_iface_written(s) :- property_explicit_interface(_, _, s).
+explicit_iface_base(s, s) :- explicit_iface_written(s), !contains("<", s).
+explicit_iface_base(s, b) :-
+ explicit_iface_written(s), contains("<", s),
+ i = range(1, strlen(s)), substr(s, i, 1) = "<", b = substr(s, 0, i), !contains("<", b).
implements_shape(prov, impl, ifaceM) :-
method_explicit_interface(prov, impl, ifaceName),
+ explicit_iface_base(ifaceName, ifaceBase),
method_in_type(prov, gk, impl),
type_satisfies(prov, gk, igk),
type_group(prov, it, igk), type_decl(prov, iname, iqn, _, _, it),
- (ifaceName = iname ; ifaceName = iqn),
+ (ifaceBase = iname ; ifaceBase = iqn),
interface_member(prov, igk, n, "method", ifaceM),
method_name(prov, impl, n),
method_paramc(prov, impl, pc), method_paramc(prov, ifaceM, pc).
+// An explicit PROPERTY, indexer or event: `int IOpt.Count { get; }`. The name is
+// written on the property, not on its accessors, so each accessor is paired with the
+// interface accessor of the same name (get_Count, set_Count, get_this[], add_E).
+implements_shape(prov, impl, ifaceM) :-
+ accessor_is_explicit(prov, impl, ifaceName),
+ explicit_iface_base(ifaceName, ifaceBase),
+ method_in_type(prov, gk, impl),
+ type_satisfies(prov, gk, igk),
+ type_group(prov, it, igk), type_decl(prov, iname, iqn, _, _, it),
+ (ifaceBase = iname ; ifaceBase = iqn),
+ method_in_type(prov, igk, ifaceM), method_is_accessor(prov, ifaceM),
+ method_name(prov, impl, n), method_name(prov, ifaceM, n),
+ method_paramc(prov, impl, pc), method_paramc(prov, ifaceM, pc).
+accessor_is_explicit(prov, acc, iface) :-
+ method_accessor_of(prov, acc, p, _), property_explicit_interface(prov, p, iface).
+member_is_explicit_impl(prov, acc) :- accessor_is_explicit(prov, acc, _).
+
// AND NOT WHERE A PARAMETER TYPE PROVES THEM DIFFERENT (#1246). Name and count alone
// paired an explicit `IKeyed.K(string)` with `IKeyed.K(int)`, so the fan of a call the
// arguments had narrowed to K(int) still reached K(string). An implementation repeats
@@ -252,6 +278,51 @@ dispatch_runtime_shape("client", m, t) :-
method_paramc("lib", m, pc), method_paramc("client", t, pc),
method_type_arity("lib", m, ar), method_type_arity("client", t, ar).
+// ── ACCESSORS DISPATCH LIKE METHODS ─────────────────────────────────────────
+// `s.Area` on an abstract `Shape`, `p.Len` on an `IPlain`, `o[i]` on an interface
+// indexer: the accessor that runs is the runtime type's own. Accessors are kept out of
+// the name table (member_method cannot find them), so the per-runtime-type lookup above
+// never answered for one and a read through an interface or a virtual property stopped
+// at the declaration, which has no body. Looked up here by the accessor's own name
+// (get_Area, set_Len, get_this[]) along the class chain, nearest first, with the
+// parameter count separating indexer overloads.
+accessor_cand(prov, gk, n, acc, 0) :-
+ method_in_type(prov, gk, acc), method_is_accessor(prov, acc), method_name(prov, acc, n),
+ !accessor_is_explicit(prov, acc, _).
+accessor_cand(prov, gk, n, acc, d) :-
+ type_base_chain(prov, gk, bgk, d),
+ method_in_type(prov, bgk, acc), method_is_accessor(prov, acc), method_name(prov, acc, n),
+ !accessor_is_explicit(prov, acc, _).
+accessor_cand("client", gk, n, acc, d) :-
+ type_base_chain("client", gk, bgk, d),
+ method_in_type("lib", bgk, acc), method_is_accessor("lib", acc), method_name("lib", acc, n),
+ !accessor_is_explicit("lib", acc, _).
+accessor_best(prov, gk, n, pc, d) :-
+ accessor_cand(prov, gk, n, a, _), method_paramc_any(a, pc),
+ d = min k : { accessor_cand(prov, gk, n, a2, k), method_paramc_any(a2, pc) }.
+accessor_member(prov, gk, n, pc, acc) :-
+ accessor_cand(prov, gk, n, acc, d), method_paramc_any(acc, pc), accessor_best(prov, gk, n, pc, d).
+method_paramc_any(m, pc) :- method_paramc(_, m, pc).
+
+// An accessor is dispatchable when its property is: `virtual`, `abstract` or `override`
+// is written on the property, and the accessor rows carry none of it.
+method_is_dispatchable(prov, acc) :-
+ method_accessor_of(prov, acc, p, _), property_modifier(prov, p, mod),
+ (mod = "virtual" ; mod = "abstract" ; mod = "override").
+
+dispatch_runtime_shape(prov, m, t) :-
+ method_is_accessor(prov, m), method_is_dispatchable(prov, m),
+ method_in_type(prov, ogk, m), method_name(prov, m, n), method_paramc(prov, m, pc),
+ type_self_or_ancestor(prov, cgk, ogk),
+ type_is_concrete(prov, cgk),
+ accessor_member(prov, cgk, n, pc, t).
+dispatch_runtime_shape("client", m, t) :-
+ method_is_accessor("lib", m), method_is_dispatchable("lib", m),
+ method_in_type("lib", ogk, m), method_name("lib", m, n), method_paramc("lib", m, pc),
+ type_self_or_ancestor("client", cgk, ogk),
+ type_is_concrete("client", cgk),
+ accessor_member("client", cgk, n, pc, t).
+
// AN EXPLICIT INTERFACE IMPLEMENTATION IS THE SECOND WAY A CONCRETE TYPE CAN
// ANSWER FOR AN INTERFACE MEMBER, and the clause above cannot see it.
//
diff --git a/graph/csharp/souffle/decls_all.dl b/graph/csharp/souffle/decls_all.dl
index 4372d0de..882cc9b7 100644
--- a/graph/csharp/souffle/decls_all.dl
+++ b/graph/csharp/souffle/decls_all.dl
@@ -18,13 +18,21 @@
// ============================================================================
.decl dispatch_cap(c0:symbol)
+.decl dispatch_closed_world(c0:symbol)
.decl jdk_max_depth(c0:number)
.decl lib_max_depth(c0:number)
.decl runtime_observed_edge(c0:symbol,c1:symbol,c2:symbol)
.decl taint_gating(c0:symbol)
+.decl accessor_best(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:number)
+.decl accessor_cand(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:number)
.decl accessor_edge(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl accessor_edge_count(c0:symbol,c1:symbol,c2:number)
+.decl accessor_is_bodiless(c0:symbol,c1:symbol)
+.decl accessor_is_explicit(c0:symbol,c1:symbol,c2:symbol)
+.decl accessor_member(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl accessor_names_bodiless(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl accessor_site_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl accessor_tier(c0:symbol,c1:symbol,c2:symbol)
.decl amqp_bind_var(c0:symbol,c1:symbol,c2:symbol)
.decl amqp_binding(c0:symbol,c1:symbol,c2:symbol)
@@ -66,6 +74,7 @@
.decl call_chain_edge(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol,c6:symbol)
.decl call_chain_summary(c0:symbol,c1:number)
.decl call_class(c0:symbol,c1:symbol,c2:symbol)
+.decl call_class_kind(c0:symbol)
.decl call_context(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl call_ctor_implicit(c0:symbol,c1:symbol,c2:symbol)
.decl call_ctor_target(c0:symbol,c1:symbol,c2:symbol)
@@ -94,6 +103,7 @@
.decl call_is_unqualified(c0:symbol,c1:symbol)
.decl call_kind(c0:symbol,c1:symbol,c2:symbol)
.decl call_member_dispatches(c0:symbol,c1:symbol)
+.decl call_method_type_arg(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl call_module(c0:symbol,c1:symbol,c2:symbol)
.decl call_names_bodiless(c0:symbol,c1:symbol)
.decl call_needs_ctor_lookup(c0:symbol,c1:symbol)
@@ -135,16 +145,50 @@
.decl client_glued_base(c0:symbol,c1:symbol)
.decl client_path(c0:symbol)
.decl client_shift(c0:symbol,c1:symbol,c2:number)
+.decl collection_initializer(c0:symbol,c1:symbol)
+.decl cond_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl conv_act_ok(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_action(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl conv_area_fixed(c0:symbol,c1:symbol)
+.decl conv_area_ok(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl conv_call(c0:symbol)
+.decl conv_ctl_ok(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_default(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_default_lit(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_has(c0:symbol,c1:symbol)
+.decl conv_pattern(c0:symbol,c1:symbol)
+.decl conv_route(c0:symbol,c1:symbol)
+.decl conv_route_call(c0:symbol,c1:number)
+.decl conv_s1(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_s2(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_s2_at(c0:symbol,c1:number)
+.decl conv_s2_demand(c0:symbol,c1:symbol)
+.decl conv_s2_has(c0:symbol)
+.decl conv_s3(c0:symbol,c1:symbol,c2:symbol)
+.decl conv_s3_at(c0:symbol,c1:number)
+.decl conv_s3_demand(c0:symbol,c1:symbol)
+.decl conv_s3_has(c0:symbol)
+.decl conv_tok_at(c0:symbol,c1:symbol,c2:number)
+.decl conv_tok_span(c0:symbol,c1:symbol,c2:number,c3:number)
+.decl conv_use(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl conv_verb(c0:symbol,c1:symbol)
.decl conversion_candidate_type(c0:symbol,c1:symbol,c2:symbol)
.decl conversion_target_name(c0:symbol,c1:symbol,c2:symbol)
+.decl cs_access_outside_assembly_denied(c0:symbol)
.decl cs_alias_framework_type(c0:symbol,c1:symbol)
.decl cs_amqp_bind(c0:symbol)
.decl cs_amqp_publish(c0:symbol)
.decl cs_attr_suffix(c0:symbol,c1:symbol)
+.decl cs_client_factory_generic(c0:symbol)
.decl cs_di_resolve(c0:symbol)
+.decl cs_di_resolve_declared(c0:symbol)
.decl cs_ef_context_base(c0:symbol)
.decl cs_ef_interceptor_register(c0:symbol)
.decl cs_ef_save_call(c0:symbol,c1:symbol)
+.decl cs_field_access_class(c0:symbol,c1:symbol,c2:symbol)
+.decl cs_field_access_count(c0:symbol,c1:symbol,c2:number)
+.decl cs_field_access_kind(c0:symbol,c1:symbol,c2:symbol)
+.decl cs_field_access_target(c0:symbol,c1:symbol,c2:symbol)
.decl cs_framework_base(c0:symbol)
.decl cs_framework_callback(c0:symbol,c1:symbol,c2:symbol)
.decl cs_framework_callback_prefix(c0:symbol,c1:symbol,c2:symbol)
@@ -157,25 +201,43 @@
.decl cs_grpc_client_member_not_rpc(c0:symbol)
.decl cs_grpc_client_suffix(c0:symbol)
.decl cs_grpc_map_service(c0:symbol)
+.decl cs_grpc_runtime_namespace(c0:symbol)
.decl cs_grpc_server_suffix(c0:symbol)
.decl cs_heritage_in_source(c0:symbol,c1:symbol)
+.decl cs_http_api_controller_attr(c0:symbol)
+.decl cs_http_area_attr(c0:symbol)
+.decl cs_http_area_route(c0:symbol,c1:number,c2:number)
.decl cs_http_client_factory_method(c0:symbol)
.decl cs_http_client_method(c0:symbol,c1:symbol)
.decl cs_http_client_type(c0:symbol)
+.decl cs_http_controller_base(c0:symbol)
.decl cs_http_controller_suffix(c0:symbol)
+.decl cs_http_conventional_route(c0:symbol,c1:number)
.decl cs_http_declarative_attr(c0:symbol,c1:symbol)
+.decl cs_http_default_route(c0:symbol,c1:symbol)
.decl cs_http_map_group(c0:symbol)
+.decl cs_http_map_methods(c0:symbol)
.decl cs_http_map_verb(c0:symbol,c1:symbol)
.decl cs_http_method_member(c0:symbol,c1:symbol)
+.decl cs_http_non_action_attr(c0:symbol)
.decl cs_http_request_message_type(c0:symbol)
.decl cs_http_route_attr(c0:symbol)
+.decl cs_http_route_builder_type(c0:symbol)
+.decl cs_http_route_value_key(c0:symbol)
.decl cs_http_send_message_method(c0:symbol)
.decl cs_http_verb_any(c0:symbol)
.decl cs_http_verb_attr(c0:symbol,c1:symbol)
.decl cs_lower(c0:symbol,c1:symbol)
+.decl cs_main_param(c0:symbol)
+.decl cs_main_return(c0:symbol)
+.decl cs_main_shape(c0:symbol)
.decl cs_med_dispatch(c0:symbol,c1:symbol,c2:symbol)
.decl cs_med_sender_type(c0:symbol)
+.decl cs_member_name_expr(c0:symbol,c1:symbol)
.decl cs_mock_arg_matcher(c0:symbol)
+.decl cs_mock_behavior_arg(c0:symbol)
+.decl cs_mock_factory(c0:symbol,c1:symbol)
+.decl cs_mock_new_type(c0:symbol)
.decl cs_msg_builder(c0:symbol,c1:symbol)
.decl cs_msg_bus_send(c0:symbol)
.decl cs_msg_bus_type(c0:symbol,c1:symbol)
@@ -192,12 +254,15 @@
.decl cs_remote_confidence_binding(c0:symbol)
.decl cs_remote_confidence_exact(c0:symbol)
.decl cs_remote_confidence_route_shape(c0:symbol)
+.decl cs_remote_confidence_service_name(c0:symbol)
.decl cs_remote_transport_grpc(c0:symbol)
.decl cs_remote_transport_http(c0:symbol)
+.decl cs_route_token_end(c0:symbol)
.decl cs_servicebus_handler_event(c0:symbol)
.decl cs_string_passthrough(c0:symbol)
.decl cs_string_type(c0:symbol)
.decl cs_test_attr(c0:symbol)
+.decl cs_test_attr_type(c0:symbol)
.decl cs_test_lifecycle_attr(c0:symbol)
.decl cs_url_not_path_start(c0:symbol)
.decl cseg(c0:symbol,c1:number,c2:symbol)
@@ -209,6 +274,16 @@
.decl ctl_route_c(c0:symbol,c1:symbol,c2:symbol)
.decl ctl_route_tok(c0:symbol,c1:symbol,c2:symbol)
.decl ctor_accepts_argc(c0:symbol,c1:symbol,c2:symbol)
+.decl ctor_has_initializer(c0:symbol)
+.decl ctor_implicit_type(c0:symbol,c1:symbol)
+.decl decl_return_param(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl delegate_arg_at(c0:symbol,c1:number,c2:symbol)
+.decl delegate_arg_expr(c0:symbol,c1:symbol)
+.decl delegate_group_demand(c0:symbol,c1:symbol)
+.decl delegate_group_method(c0:symbol,c1:symbol,c2:symbol)
+.decl delegate_handed_expr(c0:symbol,c1:symbol)
+.decl delegate_handed_value(c0:symbol,c1:symbol)
+.decl delegate_initializer_name(c0:symbol,c1:symbol,c2:symbol)
.decl delegate_invoke_node(c0:symbol,c1:symbol)
.decl delegate_member_call(c0:symbol,c1:symbol,c2:symbol)
.decl delegate_member_holds(c0:symbol,c1:symbol,c2:symbol)
@@ -217,7 +292,15 @@
.decl delegate_member_name(c0:symbol,c1:symbol,c2:symbol)
.decl delegate_member_owner(c0:symbol,c1:symbol,c2:symbol)
.decl delegate_member_ref(c0:symbol,c1:symbol,c2:symbol)
+.decl delegate_param_at(c0:symbol,c1:number,c2:symbol)
+.decl delegate_param_call(c0:symbol,c1:symbol)
+.decl delegate_param_holds(c0:symbol,c1:symbol)
+.decl delegate_param_holds_any(c0:symbol)
+.decl delegate_param_node(c0:symbol,c1:symbol)
+.decl delegate_param_receives(c0:symbol,c1:symbol)
+.decl delegate_param_ref(c0:symbol,c1:symbol)
.decl delegate_qualifier_type(c0:symbol,c1:symbol,c2:symbol)
+.decl delegate_site_callee(c0:symbol,c1:symbol,c2:number)
.decl delegate_store(c0:symbol,c1:symbol,c2:symbol)
.decl delegate_stored_expr(c0:symbol,c1:symbol)
.decl delegate_storing_op(c0:symbol)
@@ -248,6 +331,8 @@
.decl event_remover(c0:symbol,c1:symbol,c2:symbol)
.decl event_type_name(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl event_unaccess(c0:symbol,c1:symbol,c2:symbol)
+.decl explicit_iface_base(c0:symbol,c1:symbol)
+.decl explicit_iface_written(c0:symbol)
.decl expr_anon_decl(c0:symbol,c1:symbol,c2:symbol)
.decl expr_argument(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl expr_arity(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -284,6 +369,7 @@
.decl expr_owner(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl expr_parent(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl expr_predefined_recv_name(c0:symbol,c1:symbol,c2:symbol)
+.decl expr_qualified_field(c0:symbol,c1:symbol,c2:symbol)
.decl expr_qualifier_child(c0:symbol,c1:symbol,c2:symbol)
.decl expr_receiver_child(c0:symbol,c1:symbol,c2:symbol)
.decl expr_recv_is_array(c0:symbol,c1:symbol)
@@ -296,6 +382,7 @@
.decl expr_target_any(c0:symbol,c1:symbol,c2:symbol)
.decl expr_target_type(c0:symbol,c1:symbol,c2:symbol)
.decl expr_type(c0:symbol,c1:symbol,c2:symbol)
+.decl expr_type_argc(c0:symbol,c1:symbol,c2:symbol)
.decl expr_type_owner(c0:symbol,c1:symbol,c2:symbol)
.decl expr_type_via_ref(c0:symbol,c1:symbol,c2:symbol)
.decl expr_ultimate_method(c0:symbol,c1:symbol,c2:symbol)
@@ -308,8 +395,11 @@
.decl extension_candidate(c0:symbol,c1:symbol,c2:symbol)
.decl extension_candidate_count(c0:symbol,c1:symbol,c2:number)
.decl extension_candidate_firm(c0:symbol,c1:symbol,c2:symbol)
+.decl extension_candidate_matched(c0:symbol,c1:symbol)
.decl extension_in_scope(c0:symbol,c1:symbol,c2:symbol)
.decl extension_is_guess(c0:symbol,c1:symbol,c2:symbol)
+.decl extension_lib_self_or_ancestor(c0:symbol,c1:symbol)
+.decl extension_loose_match(c0:symbol,c1:symbol,c2:symbol)
.decl extension_match(c0:symbol,c1:symbol,c2:symbol)
.decl extension_method(c0:symbol,c1:symbol,c2:symbol)
.decl extension_no_match(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -323,6 +413,7 @@
.decl external_recv_name(c0:symbol,c1:symbol,c2:symbol)
.decl external_target(c0:symbol,c1:symbol,c2:symbol)
.decl external_type_name(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl field_access(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol)
.decl field_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl field_in_type(c0:symbol,c1:symbol,c2:symbol)
.decl field_initializer(c0:symbol,c1:symbol,c2:symbol)
@@ -337,23 +428,58 @@
.decl generated_accessor_read(c0:symbol,c1:symbol,c2:symbol)
.decl generated_property(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl generated_property_type(c0:symbol,c1:symbol,c2:symbol)
+.decl gm_arg_at(c0:symbol,c1:symbol,c2:number,c3:symbol)
+.decl gm_infer_param(c0:symbol,c1:symbol,c2:number)
+.decl gm_plain_twin(c0:symbol,c1:symbol)
+.decl gm_return_param(c0:symbol,c1:symbol,c2:symbol)
+.decl gp_bind_arg(c0:symbol,c1:symbol)
+.decl gp_bind_param(c0:symbol,c1:symbol,c2:number)
+.decl gp_c_nargs(c0:symbol,c1:number)
.decl gp_demand(c0:symbol)
+.decl gp_ext_named(c0:symbol,c1:symbol,c2:symbol)
+.decl gp_helper_call(c0:symbol,c1:symbol)
.decl gp_is_call(c0:symbol)
.decl gp_is_local(c0:symbol)
+.decl gp_is_param(c0:symbol)
+.decl gp_named_call(c0:symbol,c1:symbol,c2:symbol)
+.decl gp_named_cand(c0:symbol,c1:symbol)
+.decl gp_named_ncand(c0:symbol,c1:number)
+.decl gp_param_arg(c0:symbol,c1:symbol)
+.decl gp_w_nparams(c0:symbol,c1:number)
.decl group_member_substituted(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl group_prefix(c0:symbol,c1:symbol)
+.decl grpc_base_at(c0:symbol,c1:symbol)
+.decl grpc_cli_ns(c0:symbol,c1:symbol)
+.decl grpc_global_using(c0:symbol)
+.decl grpc_has_scoped(c0:symbol,c1:symbol)
.decl grpc_name_demand(c0:symbol)
.decl grpc_name_dot(c0:symbol,c1:number)
.decl grpc_name_ends_async(c0:symbol)
.decl grpc_name_has_prev(c0:symbol)
.decl grpc_name_last_dot(c0:symbol,c1:number)
+.decl grpc_name_prefix(c0:symbol,c1:symbol)
.decl grpc_name_prev_dot(c0:symbol,c1:number)
.decl grpc_name_segs(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_ns_demand(c0:symbol)
+.decl grpc_ns_dot(c0:symbol,c1:number)
+.decl grpc_ns_not_evidence(c0:symbol)
+.decl grpc_ns_prefix(c0:symbol,c1:symbol)
+.decl grpc_pair(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl grpc_pair_n(c0:symbol,c1:symbol,c2:number)
+.decl grpc_pair_scoped(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_pair_t0(c0:symbol,c1:symbol,c2:symbol)
.decl grpc_send_cand(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_send_name(c0:symbol,c1:symbol)
+.decl grpc_send_ns_home(c0:symbol,c1:symbol)
.decl grpc_send_primary(c0:symbol,c1:symbol,c2:symbol)
.decl grpc_send_site(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl grpc_serves(c0:symbol,c1:symbol)
+.decl grpc_serves_at(c0:symbol,c1:symbol,c2:symbol)
.decl grpc_service_of_name(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_service_type(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_srv_ns(c0:symbol,c1:symbol)
+.decl grpc_static_base(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_written_base(c0:symbol,c1:symbol)
.decl heritage_has_ctor_args(c0:symbol,c1:symbol,c2:symbol)
.decl heritage_of_entity(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol)
.decl heritage_resolves(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -385,8 +511,13 @@
.decl implements_member(c0:symbol,c1:symbol,c2:symbol)
.decl implements_params_conflict(c0:symbol,c1:symbol)
.decl implements_shape(c0:symbol,c1:symbol,c2:symbol)
+.decl implicit_base_ctor(c0:symbol,c1:symbol)
.decl implicit_base_group(c0:symbol,c1:symbol)
.decl implicit_base_name(c0:symbol)
+.decl implicit_base_site(c0:symbol,c1:symbol)
+.decl implicit_call_count(c0:symbol,c1:symbol,c2:number)
+.decl implicit_call_shape(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl implicit_call_target(c0:symbol,c1:symbol,c2:symbol)
.decl implicit_new_untargeted(c0:symbol,c1:symbol,c2:symbol)
.decl indexer_access(c0:symbol,c1:symbol,c2:symbol)
.decl indexer_getter(c0:symbol,c1:symbol,c2:symbol)
@@ -394,7 +525,9 @@
.decl indexer_setter(c0:symbol,c1:symbol,c2:symbol)
.decl indexer_write_target(c0:symbol,c1:symbol,c2:symbol)
.decl inherited_member_ref(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl initializer_has_assignment(c0:symbol)
.decl interface_extends(c0:symbol,c1:symbol,c2:symbol)
+.decl interface_extends_tc(c0:symbol,c1:symbol,c2:symbol)
.decl interface_has_default_impl(c0:symbol,c1:symbol)
.decl interface_member(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl interp_final(c0:symbol,c1:symbol)
@@ -405,7 +538,11 @@
.decl interp_step(c0:symbol,c1:number,c2:symbol)
.decl invocation_site(c0:symbol,c1:symbol,c2:symbol)
.decl invocation_site_classified(c0:symbol,c1:symbol)
+.decl lambda_arg_at(c0:symbol,c1:symbol,c2:number,c3:symbol)
.decl lambda_assigned_type_ref(c0:symbol,c1:symbol,c2:symbol)
+.decl lambda_callee_overloaded(c0:symbol,c1:symbol,c2:symbol)
+.decl lambda_callee_param_ref(c0:symbol,c1:symbol,c2:number,c3:symbol)
+.decl lambda_callee_single(c0:symbol,c1:symbol)
.decl lambda_delegate_in_source(c0:symbol,c1:symbol)
.decl lambda_delegate_signature(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl lambda_param_arg_ref(c0:symbol,c1:symbol,c2:symbol)
@@ -417,6 +554,7 @@
.decl lc(c0:symbol,c1:symbol)
.decl lc_demand(c0:symbol)
.decl lc_step(c0:symbol,c1:number,c2:symbol)
+.decl lib_member_inaccessible(c0:symbol)
.decl lib_type_named(c0:symbol,c1:symbol)
.decl local_bind_best_depth(c0:symbol,c1:symbol,c2:number)
.decl local_bind_cand(c0:symbol,c1:symbol,c2:symbol,c3:number)
@@ -426,6 +564,14 @@
.decl map_call(c0:symbol,c1:symbol)
.decl map_group_call(c0:symbol)
.decl map_handler(c0:symbol,c1:symbol)
+.decl map_handler_at(c0:symbol,c1:number)
+.decl map_handler_expr(c0:symbol,c1:symbol)
+.decl map_methods_call(c0:symbol)
+.decl map_methods_has_verb(c0:symbol)
+.decl map_methods_verb(c0:symbol,c1:symbol)
+.decl map_verb_call(c0:symbol,c1:symbol)
+.decl map_verb_elem(c0:symbol,c1:symbol)
+.decl map_verbs_expr(c0:symbol,c1:symbol)
.decl marker_scrub(c0:symbol,c1:symbol)
.decl mb_demand(c0:symbol)
.decl med_arg_clash(c0:symbol,c1:symbol)
@@ -458,6 +604,7 @@
.decl member_access(c0:symbol,c1:symbol,c2:symbol)
.decl member_access_is_callee(c0:symbol,c1:symbol)
.decl member_access_resolved(c0:symbol,c1:symbol)
+.decl member_access_site_untyped(c0:symbol,c1:symbol)
.decl member_best_rank(c0:symbol,c1:symbol,c2:symbol,c3:number)
.decl member_cand(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:number)
.decl member_is_explicit_impl(c0:symbol,c1:symbol)
@@ -470,6 +617,9 @@
.decl member_lookup_miss_boundary(c0:symbol,c1:symbol,c2:symbol)
.decl member_lookup_miss_real(c0:symbol,c1:symbol,c2:symbol)
.decl member_method(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl member_method_newdecl(c0:symbol,c1:symbol,c2:symbol,c3:number)
+.decl member_method_newdecl_before(c0:symbol,c1:symbol,c2:symbol,c3:number)
+.decl member_method_sig_hidden(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:number)
.decl member_type_substituted(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl method_accepts_argc(c0:symbol,c1:symbol,c2:symbol)
.decl method_accepts_extension_argc(c0:symbol,c1:symbol,c2:symbol)
@@ -482,6 +632,7 @@
.decl method_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl method_dispatch_candidate(c0:symbol,c1:symbol,c2:symbol)
.decl method_explicit_interface(c0:symbol,c1:symbol,c2:symbol)
+.decl method_group_handoff(c0:symbol,c1:symbol,c2:symbol)
.decl method_has_block(c0:symbol,c1:symbol)
.decl method_has_body(c0:symbol,c1:symbol)
.decl method_has_params_array(c0:symbol,c1:symbol)
@@ -516,6 +667,7 @@
.decl method_owner(c0:symbol,c1:symbol,c2:symbol)
.decl method_param_predef(c0:symbol,c1:number,c2:symbol)
.decl method_paramc(c0:symbol,c1:symbol,c2:symbol)
+.decl method_paramc_any(c0:symbol,c1:symbol)
.decl method_partial_def(c0:symbol,c1:symbol)
.decl method_partial_impl(c0:symbol,c1:symbol)
.decl method_return_external_name(c0:symbol,c1:symbol,c2:symbol)
@@ -529,6 +681,11 @@
.decl method_span(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl method_type_arity(c0:symbol,c1:symbol,c2:symbol)
.decl method_verb_attr(c0:symbol,c1:symbol,c2:symbol)
+.decl mock_ctor(c0:symbol,c1:symbol)
+.decl mock_ctor_argc(c0:symbol,c1:symbol)
+.decl mock_ctor_behavior(c0:symbol)
+.decl mock_ctor_lambda(c0:symbol)
+.decl mock_ctor_site(c0:symbol,c1:symbol)
.decl module_assembly(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl module_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl module_file(c0:symbol,c1:symbol,c2:symbol)
@@ -541,6 +698,7 @@
.decl module_lang(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl module_namespace_own(c0:symbol,c1:symbol,c2:symbol)
.decl module_namespace_style(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl module_ns_chain(c0:symbol,c1:symbol,c2:symbol)
.decl module_parse_errors(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl module_regime(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl module_target(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -571,6 +729,13 @@
.decl msg_request_site(c0:symbol,c1:symbol,c2:symbol)
.decl msg_type_arg(c0:symbol,c1:symbol)
.decl msg_typed_arg(c0:symbol)
+.decl mvc_action(c0:symbol,c1:symbol)
+.decl mvc_area(c0:symbol,c1:symbol)
+.decl mvc_ctl(c0:symbol,c1:symbol)
+.decl mvc_has_area(c0:symbol)
+.decl mvc_method_attr_routed(c0:symbol)
+.decl mvc_non_action(c0:symbol)
+.decl mvc_type_attr_routed(c0:symbol)
.decl name_to_type(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl nested_declares(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl new_target_decl_name(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -586,23 +751,36 @@
.decl overrides(c0:symbol,c1:symbol,c2:symbol)
.decl overrides_transitive(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_arg_at(c0:symbol,c1:symbol,c2:number,c3:symbol)
+.decl ovl_arg_iface(c0:symbol,c1:symbol,c2:number,c3:symbol)
+.decl ovl_arg_source(c0:symbol,c1:symbol,c2:number,c3:symbol)
.decl ovl_arg_type(c0:symbol,c1:symbol,c2:number,c3:symbol)
.decl ovl_cand(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_conv(c0:symbol,c1:symbol)
+.decl ovl_delegate_arity(c0:symbol,c1:number)
+.decl ovl_delegate_name(c0:symbol)
.decl ovl_drop(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_exact(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_exact_pos(c0:symbol,c1:symbol,c2:symbol,c3:number)
+.decl ovl_ext_receiver(c0:symbol,c1:symbol)
.decl ovl_int_suffix(c0:symbol,c1:symbol)
.decl ovl_int_suffix2(c0:symbol,c1:symbol)
+.decl ovl_lambda_arg(c0:symbol,c1:symbol,c2:number,c3:number)
.decl ovl_literal_type(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_open_access(c0:symbol)
+.decl ovl_param_at(c0:symbol,c1:symbol,c2:symbol,c3:number,c4:symbol)
+.decl ovl_param_closed(c0:symbol)
+.decl ovl_param_delegate(c0:symbol,c1:symbol)
+.decl ovl_param_is_delegate(c0:symbol)
.decl ovl_param_type(c0:symbol,c1:number,c2:symbol)
.decl ovl_predef(c0:symbol,c1:symbol)
.decl ovl_predef_kw(c0:symbol,c1:symbol)
.decl ovl_real_suffix(c0:symbol,c1:symbol)
+.decl ovl_reflection_type(c0:symbol)
.decl ovl_rejected(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_site(c0:symbol,c1:symbol)
+.decl ovl_source_closed(c0:symbol)
.decl ovl_survives(c0:symbol,c1:symbol)
+.decl ovl_type_converts(c0:symbol)
.decl ovl_visible(c0:symbol,c1:symbol,c2:symbol)
.decl ovl_widen(c0:symbol,c1:symbol)
.decl p_after_scheme(c0:symbol,c1:symbol)
@@ -687,6 +865,7 @@
.decl query_clause_body(c0:symbol,c1:symbol,c2:symbol)
.decl query_clause_descending(c0:symbol,c1:symbol)
.decl query_clause_source(c0:symbol,c1:symbol,c2:symbol)
+.decl recv_value_written_name(c0:symbol,c1:symbol)
.decl ref_binds_value(c0:symbol,c1:symbol)
.decl ref_denotes(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl ref_denotes_base(c0:symbol,c1:symbol,c2:symbol)
@@ -729,6 +908,7 @@
.decl serves_destination(c0:symbol,c1:symbol,c2:symbol)
.decl site_in_output(c0:symbol)
.decl site_leaves_client(c0:symbol,c1:symbol)
+.decl site_unresolved_member(c0:symbol,c1:symbol,c2:symbol)
.decl site_unresolved_named(c0:symbol,c1:symbol,c2:symbol)
.decl site_unresolved_real(c0:symbol)
.decl sseg(c0:symbol,c1:number,c2:symbol)
@@ -790,7 +970,9 @@
.decl type_is_static_entity(c0:symbol,c1:symbol)
.decl type_is_struct(c0:symbol,c1:symbol)
.decl type_is_top_level(c0:symbol,c1:symbol)
+.decl type_member_base_unresolved(c0:symbol,c1:symbol,c2:symbol)
.decl type_member_name(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl type_members_incomplete(c0:symbol,c1:symbol)
.decl type_modifier(c0:symbol,c1:symbol,c2:symbol)
.decl type_module(c0:symbol,c1:symbol,c2:symbol)
.decl type_multiple_bases(c0:symbol,c1:symbol)
@@ -815,6 +997,7 @@
.decl type_parent(c0:symbol,c1:symbol,c2:symbol)
.decl type_placement(c0:symbol,c1:symbol,c2:symbol)
.decl type_primary_ctor(c0:symbol,c1:symbol,c2:symbol)
+.decl type_qn_tail(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl type_ref(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol,c6:symbol)
.decl type_ref_arg(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl type_ref_argc(c0:symbol,c1:symbol,c2:symbol)
@@ -840,6 +1023,9 @@
.decl type_struct_heritage_is_class(c0:symbol,c1:symbol,c2:symbol)
.decl type_subtype(c0:symbol,c1:symbol,c2:symbol)
.decl unbound_ref(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl unresolved_member_access(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl unresolved_member_access_kind(c0:symbol,c1:symbol,c2:symbol)
+.decl unresolved_member_accessor(c0:symbol,c1:symbol)
.decl url_tmpl(c0:symbol,c1:symbol)
.decl using_alias(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl using_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
@@ -849,6 +1035,7 @@
.decl using_is_static(c0:symbol,c1:symbol)
.decl using_namespace(c0:symbol,c1:symbol,c2:symbol)
.decl using_static_type(c0:symbol,c1:symbol,c2:symbol)
+.decl using_var_initializer(c0:symbol)
.decl var_block(c0:symbol,c1:symbol,c2:symbol)
.decl var_decl(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl var_declared_before(c0:symbol,c1:symbol,c2:symbol)
diff --git a/graph/csharp/souffle/export_manifest.tsv b/graph/csharp/souffle/export_manifest.tsv
index a48d6138..56f36d4f 100644
--- a/graph/csharp/souffle/export_manifest.tsv
+++ b/graph/csharp/souffle/export_manifest.tsv
@@ -39,3 +39,6 @@ entry_reachable entry-reachable.csv
type_ancestor_type resolution-type-ancestor-type.csv
override_pair resolution-virtual-override.csv
framework_edge framework-edge.csv
+field_access field-access.csv
+site_unresolved_member site-unresolved-member.csv
+ctor_implicit_type ctor-implicit-type.csv
diff --git a/graph/csharp/souffle/gen_decls_all.py b/graph/csharp/souffle/gen_decls_all.py
index 4ccb7452..7a8f6cc0 100644
--- a/graph/csharp/souffle/gen_decls_all.py
+++ b/graph/csharp/souffle/gen_decls_all.py
@@ -69,6 +69,9 @@
# --dispatch-cap K: refuse a virtual-dispatch fan wider than K. Recall-risky, so
# opt-in; empty means uncapped, which is the answer that cannot be wrong.
"dispatch_cap": ("symbol",),
+ # --closed-world on: narrow the dispatch fan to constructed types (#473). Staged
+ # for every language by the shared executor; no C# rule reads it yet.
+ "dispatch_closed_world": ("symbol",),
# --lib-depth: how far a client->lib chain is expanded. Empty means uncapped.
"lib_max_depth": ("number",),
# The shared executor ALWAYS stages these two, for every language, and emits one
diff --git a/graph/java/engine/call-edge-generation/annotation_flow.dl b/graph/java/engine/call-edge-generation/annotation_flow.dl
index b4de7727..6174ff01 100644
--- a/graph/java/engine/call-edge-generation/annotation_flow.dl
+++ b/graph/java/engine/call-edge-generation/annotation_flow.dl
@@ -21,6 +21,15 @@
junit_entry_ann("Test"). junit_entry_ann("Before"). junit_entry_ann("After").
junit_entry_ann("BeforeClass"). junit_entry_ann("AfterClass"). junit_entry_ann("BeforeEach").
junit_entry_ann("AfterEach"). junit_entry_ann("ParameterizedTest"). junit_entry_ann("RepeatedTest").
+junit_entry_ann("TestFactory"). junit_entry_ann("TestTemplate").
+
+// A COMPOSED annotation: an annotation type meta-annotated with one of the above is read by the runner as that
+// annotation (JUnit 5 `@Target(METHOD) @Test @interface IntegrationCase {}`), transitively. Its own name says
+// nothing, so without this a method under it is no entry point (#1418). Only an annotation TYPE is followed: a class
+// that carries @Test-named annotations is not one, and an annotation that carries none (`@Audited`) stays out.
+junit_entry_ann(a) :- annotation_on(p, m, _, _, t, _), junit_entry_ann(m),
+ type_decl(p, a, _, _, tc, _, t), annotation_type_category(tc).
+annotation_type_category("ANNOTATION_INTERFACE_TYPE"). annotation_type_category("ANNOTATION_TYPE").
http_entry_ann("GET"). http_entry_ann("POST"). http_entry_ann("PUT"). http_entry_ann("DELETE").
http_entry_ann("PATCH"). http_entry_ann("HEAD"). http_entry_ann("OPTIONS"). http_entry_ann("Path").
diff --git a/graph/java/engine/call-edge-generation/callback_dispatch.dl b/graph/java/engine/call-edge-generation/callback_dispatch.dl
new file mode 100644
index 00000000..27a2abb7
--- /dev/null
+++ b/graph/java/engine/call-edge-generation/callback_dispatch.dl
@@ -0,0 +1,93 @@
+// ============================================================================
+// Call-edge generation · A CLIENT OBJECT HANDED TO A LIBRARY, WHICH CALLS IT BACK
+//
+// verify(repo).save(argThat(new IsValid())); // IsValid implements ArgumentMatcher
+// orders.sort(new ByTotal()); // ByTotal implements Comparator
+//
+// The library runs IsValid.matches / ByTotal.compare, and no call site in client code names
+// either method: the call is made inside the library. An override is followed from a call
+// on a receiver of the library type (virtual-dispatch.dl), and there is no such receiver
+// here, so the named class's methods had no caller at all. The same body written as an
+// anonymous class was reached, only because it is lexically inside the caller (`defines`).
+//
+// THE EDGE. A call site that does NOT resolve to client code, with an argument that is a client
+// object created for it (`new C(..)`, or a local initialised with one), reaches each method of C
+// that the library can call back:
+// (a) an override of a library method (virtual_override from a lib base), when the
+// library is staged; or
+// (b) with the library absent, a method carrying @Override in a type that has an EXTERNAL
+// ancestor (external-types.dl): the annotation says a supertype declares it, and the
+// external ancestor is where that supertype can be.
+// (c) with the library absent and no @Override written anywhere in C, a public method of a C
+// that itself names an external supertype other than a marker interface.
+// A helper of a class that marks its overrides is not reached. java.lang.Object's members are
+// never fanned (equals / hashCode / toString over every argument is the classic blow-up;
+// the same exclusion client_overridden_lib_type makes).
+//
+// TIER callback_registered, kind "callback": the site HANDS the object over, and the library
+// may call it, now or later, or never. It is the tier JavaScript and TypeScript already use
+// for `xs.forEach(f)`, labelled so it is never read as a resolved call. The site keeps its
+// own row (boundary_lib / ambiguous_unknown) beside it.
+// A call that resolves to CLIENT code is left to that code: its body holds the parameter and
+// the call on it, and dispatch follows that. An anonymous class is reached as before, by its
+// `defines` hop: it is lexically inside the method that hands it over.
+// ============================================================================
+
+cb_object_member("equals"). cb_object_member("hashCode"). cb_object_member("toString").
+cb_object_member("clone"). cb_object_member("finalize").
+
+// an argument of a call that leaves the client, and the client type it holds
+cb_arg_type(call, t) :- invocation_site(call, kind), cb_site_kind(kind),
+ expr_child("client", call, "ARGUMENT", arg),
+ object_creation_type_resolves("client", arg, _, t).
+// … or a local the method created with `new` for it (`ByLength cmp = new ByLength(); xs.sort(cmp)`).
+// Only an object CREATED to be handed over: a parameter or a field passed along (`c.accept(w)`)
+// is a value flowing through, and the library method it reaches is often not one that calls
+// any of its callbacks (a Consumer does not close the AutoCloseable it is given).
+cb_arg_type(call, t) :- invocation_site(call, kind), cb_site_kind(kind),
+ expr_child("client", call, "ARGUMENT", arg),
+ java_expression(_, "ARGUMENT", _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, arg),
+ expr_ultimate_method("client", arg, m),
+ java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local),
+ local_creation_init(local),
+ expr_type("client", arg, t).
+cb_site_kind("method"). cb_site_kind("new").
+
+cb_client_site(call) :- client_calls_client("client", call, "client", _).
+cb_handoff(call, t) :- cb_arg_type(call, t), !cb_client_site(call),
+ java_type(_, _, _, _, _, _, _, _, _, _, _, _, _, t).
+
+// the methods of t, declared in it or inherited from a client ancestor
+cb_member(t, name, m) :- cb_handoff(_, t),
+ java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m).
+cb_member(t, name, m) :- cb_handoff(_, t), type_ancestor(t, sup),
+ java_method(name, _, _, _, _, _, _, sup, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m).
+
+// (a) an override of a library method
+cb_callback(t, m) :- cb_member(t, name, m), !cb_object_member(name),
+ virtual_override(base, m), method_owner("lib", _, base).
+// (b) @Override, under an external ancestor
+cb_external_ancestor(t) :- cb_handoff(_, t), type_ancestor(t, x), external_type(x).
+cb_callback(t, m) :- cb_member(t, name, m), !cb_object_member(name), cb_external_ancestor(t),
+ ann_on_method("client", _, "Override", m, _).
+// (c) with the library absent and a class that writes no @Override at all, the annotation says
+// nothing either way: every PUBLIC instance method it declares is a candidate (a library calls
+// an implementation through its interface, and an interface method is public). A class that
+// does write @Override has said which methods are overrides, and (b) reads that instead.
+cb_writes_override(t) :- cb_handoff(_, t),
+ java_method(_, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, _, _, _, _, _, m),
+ ann_on_method("client", _, "Override", m, _).
+// Only for a class that names the external supertype ITSELF (`class ByLength implements
+// Comparator`), and not a marker interface, which declares nothing to call: an entity
+// under a client base that is Serializable would otherwise offer every getter and setter.
+// Measured on a 50-file web application before this guard: 20 of 24 such edges were an entity's
+// accessors, reached from a test that put the entity in a collection.
+cb_marker_type("external:java.io.Serializable"). cb_marker_type("external:Serializable").
+cb_marker_type("external:java.lang.Cloneable"). cb_marker_type("external:Cloneable").
+cb_direct_external(t) :- cb_handoff(_, t), type_parent(t, x), external_type(x), !cb_marker_type(x).
+cb_callback(t, m) :- cb_handoff(_, t), cb_direct_external(t), !cb_writes_override(t),
+ java_method(name, _, _, _, _, _, _, t, _, _, "PUBLIC", _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
+ !cb_object_member(name).
+
+call_chain_edge(call, caller, "-", m, "client", "callback_registered", "callback") :-
+ cb_handoff(call, t), cb_callback(t, m), call_from(call, caller).
diff --git a/graph/java/engine/call-edge-generation/exception_handlers.dl b/graph/java/engine/call-edge-generation/exception_handlers.dl
new file mode 100644
index 00000000..63759521
--- /dev/null
+++ b/graph/java/engine/call-edge-generation/exception_handlers.dl
@@ -0,0 +1,86 @@
+// ============================================================================
+// Call-edge generation · EXCEPTION HANDLERS (a built exception -> the handler it runs)
+//
+// throw new OrderNotFound(id); // written here
+// repo.find(id).orElseThrow(OrderNotFound::new); // or here
+// @RestControllerAdvice class Errors {
+// @ExceptionHandler(OrderNotFound.class) ResponseEntity> missing(OrderNotFound e) { … }
+// } // run from there
+//
+// When an exception leaves a request handler, the web framework looks up the handler
+// method declared for the exception's type and runs it to build the response: one in
+// the same controller, or one in an advice class, which serves every controller. No
+// call site names the handler, so without this file `impact` on a handler listed no
+// dependent, `path '*' ` found nothing, and a change to the exception's
+// handling looked local to the advice class.
+//
+// WHY A CALL EDGE WITH TIER event_dispatch. It is the shape event_dispatch.dl and
+// entity_lifecycle.dl already model: one JVM, keyed on a TYPE, the handler running in
+// the same request before the response is written. The KIND column, `exception_handler`,
+// tells it apart from an application event or an entity callback. The constructor row
+// of the `new` itself is kept: the edge is ADDED beside it.
+//
+// THE KEY IS THE BUILT EXCEPTION'S TYPE, with the subtype rule the framework applies: a
+// handler for OrderNotFound runs for an ExpressOrderNotFound, and a handler for the
+// subtype does not run for the base. The handled types are the classes the annotation
+// names, or, when it names none, the handler's parameter type.
+//
+// THE SITE IS WHERE THE EXCEPTION IS BUILT: `new X(..)` and the `X::new` handed to an
+// orElseThrow. That is where a change in handling is felt, and a project exception type
+// is built to be thrown.
+//
+// WHAT IS NOT MODELLED, and stays unlinked rather than guessed:
+// - a handler for a LIBRARY type (Exception, RuntimeException, a validation or
+// framework exception): the framework or a library throws those, not a client
+// `new`, and every `new IllegalStateException` in the project would reach it. Only a
+// type the project declares is a key;
+// - whether the building method runs under a request at all: a job that throws the
+// same type is linked too, which is the static answer to "which handler would take
+// this";
+// - which of two matching handlers is closer: both are linked;
+// - an advice's basePackages / assignableTypes / annotations filter: it is read as
+// serving every controller.
+// ============================================================================
+
+// ── the handlers, and the types each one takes ──────────────────────────────
+exh_ann_method(m, a) :- ann_on_method(_, a, n, m, _), cfg_exh_ann(n), method_owner("client", _, m).
+exh_names_classes(m) :- exh_ann_method(m, a),
+ ann_arg(_, a, arg, _, "CLASS_REFERENCE", _), cfg_exh_arg(arg).
+exh_handles(m, t) :- exh_ann_method(m, a),
+ ann_arg(_, a, arg, v, "CLASS_REFERENCE", _), cfg_exh_arg(arg),
+ config_class_ref("annotation", a, v, t, "client").
+exh_handles(m, t) :- exh_ann_method(m, _), !exh_names_classes(m),
+ event_param_type(m, t), java_type(_, _, _, _, _, _, _, _, _, _, _, _, _, t).
+
+// a handler of an advice class serves every controller; any other serves its own type
+exh_global(m) :- exh_ann_method(m, _), method_owner("client", t, m),
+ ann_on_type(_, _, n, t), cfg_exh_advice_ann(n).
+
+// ── where a project exception is built ──────────────────────────────────────
+exh_built(call, caller, t) :-
+ java_expression("OBJECT_CREATION", _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, call),
+ expr_type("client", call, t), call_from(call, caller).
+exh_built(mr, caller, t) :-
+ java_expression("METHOD_REFERENCE", _, _, _, _, _, _, _, _, _, _, "CONSTRUCTOR", _, _, _, _, _, _, _, _, _, _, _, _, mr),
+ java_expression(_, "QUALIFIER", _, _, _, _, mr, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, q),
+ expr_type("client", q, t), call_from(mr, caller).
+exh_built(mr, caller, t) :-
+ java_expression("METHOD_REFERENCE", _, _, _, _, _, _, _, _, _, _, "CONSTRUCTOR", _, _, _, _, _, _, _, _, _, _, _, _, mr),
+ method_ref_qualifier_resolves("client", mr, _, t), call_from(mr, caller).
+
+// the handler takes the built type itself, or an ancestor of it
+exh_handles_built(m, t) :- exh_handles(m, t).
+exh_handles_built(m, bt) :- exh_built(_, _, bt), type_ancestor(bt, t), exh_handles(m, t).
+
+// ── the edge ────────────────────────────────────────────────────────────────
+exh_edge(call, caller, m) :- exh_built(call, caller, bt), exh_handles_built(m, bt),
+ exh_global(m), caller != m.
+exh_edge(call, caller, m) :- exh_built(call, caller, bt), exh_handles_built(m, bt),
+ !exh_global(m), caller != m,
+ method_owner("client", ht, m), method_owner("client", ht, caller).
+exh_edge(call, caller, m) :- exh_built(call, caller, bt), exh_handles_built(m, bt),
+ !exh_global(m), caller != m,
+ method_owner("client", ht, m), method_owner("client", ct, caller), type_ancestor(ct, ht).
+
+call_chain_edge(call, caller, "-", m, "client", "event_dispatch", "exception_handler") :-
+ exh_edge(call, caller, m).
diff --git a/graph/java/engine/config-resolution/entry-points.dl b/graph/java/engine/config-resolution/entry-points.dl
index 98c0535e..0cad1c7b 100644
--- a/graph/java/engine/config-resolution/entry-points.dl
+++ b/graph/java/engine/config-resolution/entry-points.dl
@@ -47,10 +47,57 @@ cfg_registered_type(t, kind) :- xml_container_class(kind, t, _).
cfg_registered_type(t, "config_handler") :- config_class_ref(fmt, k, _, t, "client"),
fmt != "annotation", cfg_key_expects_class(k).
+// … a type an ANNOTATION registers: @WebServlet / @WebFilter / @WebListener, exactly as
+// their web.xml twins; a JPA @Converter; a JAX-RS @Provider (knobs.dl (6b)).
+cfg_registered_type(t, kind) :- ann_on_type("client", _, n, t), cfg_registering_type_ann(n, kind).
+// … a class an annotation ARGUMENT names, for the arguments that mean "instantiate this":
+// @EntityListeners(Audit.class), @Convert(converter = PriceConverter.class). Keyed on the
+// annotation AND the argument, so an arbitrary @Foo(Bar.class) still registers nothing.
+cfg_registered_type(t, kind) :- ann_arg(prov, a, arg, _, "CLASS_REFERENCE", _),
+ cfg_registering_class_arg(n, arg, kind), cfg_ann_name_of(prov, a, n),
+ cfg_ann_class_arg(prov, a, arg, t), type_decl("client", _, _, _, _, _, t).
+cfg_ann_name_of(prov, a, n) :- ann_on_type(prov, a, n, _).
+cfg_ann_name_of(prov, a, n) :- ann_on_field(prov, a, n, _, _).
+cfg_ann_name_of(prov, a, n) :- ann_on_method(prov, a, n, _, _).
+// … a class registered IN CODE with the servlet container, by a class literal or an
+// instance: ctx.addServlet("orders", OrderServlet.class), ctx.addFilter("a", new F()),
+// ctx.addListener(L.class). The receiver has to be typed as a registrar (knobs.dl (6b)).
+cfg_registered_type(t, kind) :- cfg_servlet_registration(name, kind), call_site(call, name, recv),
+ servlet_registrar_recv(recv), call_arg(call, _, arg), registered_arg_type(arg, t).
+// … and Spring Boot's new ServletRegistrationBean<>(new OrderServlet(), "/orders").
+cfg_registered_type(t, kind) :- cfg_servlet_registration_bean(bn, kind),
+ object_creation_named(e, bn), expr_child("client", e, "ARGUMENT", arg),
+ registered_arg_type(arg, t).
+object_creation_named(e, bn) :- java_expression("OBJECT_CREATION", _, _, _, _, _, _, _, _, _, v, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
+ cfg_servlet_registration_bean(bn, _), v = bn.
+object_creation_named(e, bn) :- java_expression("OBJECT_CREATION", _, _, _, _, _, _, _, _, _, v, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
+ cfg_servlet_registration_bean(bn, _), v = cat(bn, "<>").
+// the type an argument hands over: X.class, or new X(..)
+registered_arg_type(arg, t) :- java_expression("CLASS_LITERAL", _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, q, _, _, _, _, _, _, arg),
+ java_type(_, q, _, _, _, _, _, _, _, _, _, _, _, t).
+registered_arg_type(arg, t) :- object_creation_type_resolves("client", arg, _, t).
+// a receiver typed as ServletContext (or a Jetty handler), whether the type is staged as a
+// library or known only by its written name
+servlet_registrar_recv(recv) :- recv_type_simple(recv, n), cfg_servlet_registrar_type(n).
+// recv_type_simple(Expr, SimpleName): an expression typed by a library or external type of
+// that simple name, for the few framework names a rule asks about (typed_simple_demand).
+typed_simple_demand(n) :- cfg_servlet_registrar_type(n).
+typed_simple_demand(n) :- cfg_jaxrs_client_owner(n).
+recv_type_simple(e, n) :- typed_simple_demand(n), lib_type(n, _, _, _, _, _, _, _, _, _, _, _, _, t),
+ expr_type("lib", e, t).
+recv_type_simple(e, n) :- typed_simple_demand(n), x = cat("external:", n), expr_type("external", e, x).
+recv_type_simple(e, n) :- typed_simple_demand(n), expr_type("external", e, x),
+ strlen(x) > strlen(n) + 1,
+ substr(x, max(0, strlen(x) - strlen(n) - 1), min(strlen(n) + 1, strlen(x))) = cat(".", n).
+
// (1) callbacks on a registered type — by name …
config_entry_point(m, kind, t) :- cfg_registered_type(t, kind),
java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
cfg_container_callback_name(name).
+// … and by the per-kind name list (a JPA converter's convertTo*, a JAX-RS provider's filter).
+config_entry_point(m, kind, t) :- cfg_registered_type(t, kind),
+ java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
+ cfg_registered_callback_name(kind, name).
config_entry_point(m, "spring_factories", t) :- cfg_registered_type(t, "spring_factories"),
java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
cfg_factories_callback_name(name).
@@ -71,8 +118,37 @@ config_entry_point(m, kind, t) :- cfg_registered_type(t, kind),
config_entry_point(m, kind, t) :- xml_lifecycle_method(t, mname, kind),
method_name_in_type(mname, t, m), method_owner("client", _, m).
+// (3b) the annotation twin of (3): @Bean(initMethod = "start", destroyMethod = "stop")
+// names `start` and `stop` on the type the @Bean method builds.
+config_entry_point(m, kind, fm) :- cfg_factory(prov, a, fm, _), ann_arg(prov, a, arg, mname, _, _),
+ cfg_bean_lifecycle_arg(arg, kind), mname != "", cfg_factory_type(fm, t),
+ method_name_in_type(mname, t, m), method_owner("client", _, m).
+
// (4) annotation-driven container callbacks that annotation_flow.dl does not cover.
config_entry_point(m, "lifecycle", m) :- ann_on_method(_, _, n, m, _), cfg_lifecycle_ann(n).
+// JPA entity callbacks, on an entity, a mapped superclass or an @EntityListeners class.
+// Only those: the provider never runs a @PrePersist on any other class, so a callback
+// annotation on a class no entity names is not an entry point (it stays unreachable, and
+// call-edge-generation/entity_lifecycle.dl gives it no caller either).
+config_entry_point(m, "orm_hook", m) :- ann_on_method(_, _, n, m, _), cfg_jpa_callback_ann(n),
+ method_owner("client", t, m), jpa_callback_owner(t).
+jpa_callback_owner(t) :- ann_on_type("client", _, n, t), cfg_entity_ann(n).
+jpa_callback_owner(t) :- ann_arg(prov, a, arg, _, "CLASS_REFERENCE", _), cfg_ann_name_of(prov, a, n),
+ cfg_entity_listeners_ann(n), cfg_registering_class_arg(n, arg, _), cfg_ann_class_arg(prov, a, arg, t).
+// A JAX-RS @QueryParam / @PathParam / … parameter of a client type T: the runtime builds
+// it with T.valueOf(String), T.fromString(String) or new T(String).
+jaxrs_param_type(t) :- ann_on_param(_, _, n, p, _), cfg_jaxrs_param_ann(n),
+ method_param_type_resolves("client", p, _, t).
+config_entry_point(m, "framework_hook", t) :- jaxrs_param_type(t),
+ java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, _, "1", _, _, _, m),
+ cfg_jaxrs_param_factory(name), string_param_0(m).
+config_entry_point(m, "framework_hook", t) :- jaxrs_param_type(t),
+ method_kind("client", "CONSTRUCTOR", _, m), method_owner("client", t, m),
+ java_method(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, "1", _, _, _, m),
+ string_param_0(m).
+string_param_0(m) :- java_method_parameter(_, "0", m, _, tn, _, _, _, _, _, _, _, _),
+ jaxrs_string_type(tn).
+jaxrs_string_type("String"). jaxrs_string_type("java.lang.String").
config_entry_point(m, "scheduled", m) :- ann_on_method(_, _, n, m, _), cfg_scheduled_ann(n).
config_entry_point(m, "queue", m) :- ann_on_method(_, _, n, m, _), cfg_listener_ann(n).
// … and an ApplicationListener implementation's onApplicationEvent, which Spring calls
@@ -115,6 +191,21 @@ config_entry_point(m, kind, t) :- cfg_stub_base(base, kind),
java_method(name, _, _, _, _, _, _, base, _, _, _, _, _, _, _, _, _, pc, _, _, _, _),
name != "",
java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", pc, _, _, _, m).
+// ...and when protoc ran at build time, the ImplBase is an external type with no declared methods
+// to test an override against. The service's own rpc-shaped methods are the handlers
+// (framework-behavior/destinations.dl, grpc_ext_handler; #1548).
+config_entry_point(m, "grpc_service", t) :- grpc_ext_handler(t, _, _),
+ java_method(_, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
+ grpc_rpc_shaped(m).
+
+// (6b) A REGISTERED type's method marked @Override whose supertype is known only by its
+// written name (the framework API is not in the analysed library set): it overrides the
+// framework's declaration, so the framework calls it. A JAX-RS ContainerRequestFilter's
+// filter(..), an ExceptionMapper's toResponse(..).
+config_entry_point(m, kind, t) :- cfg_registered_type(t, kind),
+ type_parent(t, x), external_type(x),
+ ann_on_method(_, _, "Override", m, t),
+ method_kind("client", "INSTANCE_METHOD", _, m).
// (7) A REGISTERED type's overrides, generalised. cfg_registered_type covers web.xml
// and config-named handlers; clause (1) matched a knob name list or a LIBRARY supertype.
diff --git a/graph/java/engine/config-resolution/knobs.dl b/graph/java/engine/config-resolution/knobs.dl
index 2ec0de97..51bb3376 100644
--- a/graph/java/engine/config-resolution/knobs.dl
+++ b/graph/java/engine/config-resolution/knobs.dl
@@ -52,6 +52,66 @@ cfg_props_ann("ConfigurationProperties").
// ── (6) CONTAINER-INVOKED METHODS (entry points sourced from annotations that
// carry a config VALUE — the name-only cases stay in annotation_flow.dl) ──
cfg_lifecycle_ann("PostConstruct"). cfg_lifecycle_ann("PreDestroy").
+// @Bean(initMethod = "start", destroyMethod = "stop"): the annotation twin of the XML
+// init-method / destroy-method attributes (8), naming a method on the bean's type.
+// cfg_bean_lifecycle_arg(AnnotationArgument, EntryKind)
+cfg_bean_lifecycle_arg("initMethod", "lifecycle_init").
+cfg_bean_lifecycle_arg("destroyMethod", "lifecycle_destroy").
+// JPA entity callbacks: the persistence provider calls them on the entity, on its mapped
+// superclasses and on the classes its @EntityListeners names, with no call site anywhere.
+cfg_jpa_callback_ann("PrePersist"). cfg_jpa_callback_ann("PostPersist").
+cfg_jpa_callback_ann("PreUpdate"). cfg_jpa_callback_ann("PostUpdate").
+cfg_jpa_callback_ann("PreRemove"). cfg_jpa_callback_ann("PostRemove").
+cfg_jpa_callback_ann("PostLoad").
+
+// ── (6b) TYPES A FRAMEWORK INSTANTIATES BECAUSE AN ANNOTATION NAMES THEM ──────
+// Only these. Lifting the "annotation" exclusion on cfg_registered_type wholesale would
+// register every X.class written in any annotation argument.
+// cfg_registering_type_ann(AnnotationOnTheType, EntryKind): the type itself carries it.
+cfg_registering_type_ann("WebServlet", "web_servlet").
+cfg_registering_type_ann("WebFilter", "web_filter").
+cfg_registering_type_ann("WebListener", "web_listener").
+cfg_registering_type_ann("Converter", "orm_hook"). // JPA @Converter
+cfg_registering_type_ann("Provider", "framework_hook"). // JAX-RS @Provider
+// cfg_registering_class_arg(Annotation, Argument, EntryKind): a class named in an argument.
+cfg_registering_class_arg("EntityListeners", "value", "orm_hook").
+cfg_registering_class_arg("Convert", "converter", "orm_hook").
+// Methods a framework calls on a type of that kind whose supertype is usually not in the
+// analysed library set (so "overrides a library method" finds nothing), matched by name.
+// cfg_registered_callback_name(EntryKind, MethodName)
+cfg_registered_callback_name("orm_hook", "convertToDatabaseColumn").
+cfg_registered_callback_name("orm_hook", "convertToEntityAttribute").
+cfg_registered_callback_name("framework_hook", "filter"). // Container{Request,Response}Filter
+cfg_registered_callback_name("framework_hook", "toResponse"). // ExceptionMapper
+cfg_registered_callback_name("framework_hook", "readFrom"). // MessageBodyReader
+cfg_registered_callback_name("framework_hook", "isReadable").
+cfg_registered_callback_name("framework_hook", "writeTo"). // MessageBodyWriter
+cfg_registered_callback_name("framework_hook", "isWriteable").
+cfg_registered_callback_name("framework_hook", "getSize").
+cfg_registered_callback_name("framework_hook", "aroundReadFrom"). // ReaderInterceptor
+cfg_registered_callback_name("framework_hook", "aroundWriteTo"). // WriterInterceptor
+cfg_registered_callback_name("framework_hook", "getContext"). // ContextResolver
+cfg_registered_callback_name("framework_hook", "getConverter"). // ParamConverterProvider
+// A JAX-RS request parameter whose type is not a String is built by that type's static
+// valueOf(String) / fromString(String) or its String constructor.
+cfg_jaxrs_param_ann("QueryParam"). cfg_jaxrs_param_ann("PathParam").
+cfg_jaxrs_param_ann("HeaderParam"). cfg_jaxrs_param_ann("FormParam").
+cfg_jaxrs_param_ann("MatrixParam"). cfg_jaxrs_param_ann("CookieParam").
+cfg_jaxrs_param_factory("valueOf"). cfg_jaxrs_param_factory("fromString").
+// Registration in code: ServletContext.addServlet("name", X.class), addFilter(.., new F()),
+// addListener(L.class), and Jetty's handler.addServlet(X.class, "/p"). The receiver's type
+// decides, because addListener is a common method name.
+// cfg_servlet_registration(MethodName, EntryKind)
+cfg_servlet_registration("addServlet", "web_servlet").
+cfg_servlet_registration("addFilter", "web_filter").
+cfg_servlet_registration("addListener", "web_listener").
+cfg_servlet_registrar_type("ServletContext").
+cfg_servlet_registrar_type("ServletContextHandler").
+cfg_servlet_registrar_type("ServletHandler").
+// … and Spring Boot's registration beans, which take the instance as a constructor argument.
+cfg_servlet_registration_bean("ServletRegistrationBean", "web_servlet").
+cfg_servlet_registration_bean("FilterRegistrationBean", "web_filter").
+cfg_servlet_registration_bean("ServletListenerRegistrationBean", "web_listener").
cfg_scheduled_ann("Scheduled").
cfg_listener_ann("EventListener"). cfg_listener_ann("KafkaListener").
cfg_listener_ann("TransactionalEventListener").
@@ -70,6 +130,9 @@ cfg_xml_class_elem("listener-class"). cfg_xml_class_elem("resource-class").
cfg_xml_class_elem("provider-class"). cfg_xml_class_elem("handler-class").
cfg_xml_class_elem("ejb-class"). cfg_xml_class_elem("mapper-class").
// (c) the element that DEFINES a bean (its class comes from cfg_xml_class_attr)
+// a document root under which `#{...}` is a bound statement parameter, not a Spring
+// expression (MyBatis mapper XML; xml-wiring.dl)
+cfg_xml_param_marker_root("mapper").
cfg_xml_bean_elem("bean"). cfg_xml_bean_elem("alias").
// ── (8) XML: attributes naming a METHOD the container invokes ────────────────
@@ -83,6 +146,10 @@ cfg_xml_lifecycle_attr("factory-method", "lifecycle_factory").
cfg_xml_container_elem("servlet-class", "web_servlet").
cfg_xml_container_elem("filter-class", "web_filter").
cfg_xml_container_elem("listener-class", "web_listener").
+// The that ties a to its .
+cfg_xml_servlet_name_elem("servlet-name").
+cfg_xml_servlet_mapping_elem("servlet-mapping").
+cfg_xml_url_pattern_elem("url-pattern").
// ── (10) XML: wiring elements (a bean depends on another bean) ───────────────
// cfg_xml_wire_elem(Tag, WireKind)
@@ -140,6 +207,15 @@ cfg_stub_base_suffix("Skeleton", "rpc_service"). // older RPC generators
cfg_stub_client_suffix("BlockingStub").
cfg_stub_client_suffix("FutureStub").
cfg_stub_client_suffix("Stub").
+cfg_stub_client_suffix("BlockingV2Stub"). // grpc-java 1.73+, newBlockingV2Stub
+
+// With the generated class absent (#1548), the holder is known only by its written name, `
Grpc`,
+// and an rpc only by its response observer parameter.
+.decl cfg_grpc_holder_suffix(c0:symbol)
+cfg_grpc_holder_suffix("Grpc").
+.decl cfg_grpc_observer_type(c0:symbol)
+cfg_grpc_observer_type("StreamObserver").
+cfg_grpc_observer_type("io.grpc.stub.StreamObserver").
// ── (8) DESTINATIONS: a name that identifies a place a message or request goes ──
// A producer and the consumer that serves it live in different deployables and never
@@ -234,6 +310,32 @@ cfg_sends_verb("delete", "DELETE"). cfg_sends_verb("patchForObject", "PAT
cfg_sends_verb("get", "GET"). cfg_sends_verb("post", "POST").
cfg_sends_verb("patch", "PATCH").
+// The servlet method that serves each verb, for a servlet mapped to a URL pattern by
+// web.xml or @WebServlet. `service` handles every verb.
+cfg_servlet_verb_method("doGet", "GET"). cfg_servlet_verb_method("doPost", "POST").
+cfg_servlet_verb_method("doPut", "PUT"). cfg_servlet_verb_method("doDelete", "DELETE").
+cfg_servlet_verb_method("doHead", "HEAD"). cfg_servlet_verb_method("doOptions", "OPTIONS").
+cfg_servlet_verb_method("doPatch", "PATCH"). cfg_servlet_verb_method("service", "any").
+cfg_servlet_url_arg("value"). cfg_servlet_url_arg("urlPatterns").
+// a JAX-RS method with @Path and no verb is a sub-resource locator
+cfg_subresource_locator_ann("Path").
+
+// The JAX-RS client API: client.target(url).path(p).request(..).get(..). The chain has to
+// start on one of these types, typed as the library's (never a client class of the same
+// name), AND pass through target(..) and request(..).
+cfg_jaxrs_client_owner("Client"). cfg_jaxrs_client_owner("WebTarget").
+cfg_jaxrs_client_factory("ClientBuilder"). // ClientBuilder.newClient().target(..)
+cfg_jaxrs_invoke_verb("get", "GET"). cfg_jaxrs_invoke_verb("post", "POST").
+cfg_jaxrs_invoke_verb("put", "PUT"). cfg_jaxrs_invoke_verb("delete", "DELETE").
+cfg_jaxrs_invoke_verb("head", "HEAD"). cfg_jaxrs_invoke_verb("options", "OPTIONS").
+cfg_jaxrs_invoke_verb("method", "any").
+// builder steps between request(..) and the verb
+cfg_jaxrs_builder_step("accept"). cfg_jaxrs_builder_step("acceptLanguage").
+cfg_jaxrs_builder_step("acceptEncoding"). cfg_jaxrs_builder_step("header").
+cfg_jaxrs_builder_step("headers"). cfg_jaxrs_builder_step("cookie").
+cfg_jaxrs_builder_step("cacheControl"). cfg_jaxrs_builder_step("property").
+cfg_jaxrs_builder_step("async"). cfg_jaxrs_builder_step("rx").
+
// (i) an annotation that makes a TYPE a declarative HTTP CLIENT. Its methods carry the
// same mapping annotations a controller does, so without this a Feign interface is
// read as a handler and answers "who serves this route".
@@ -243,6 +345,9 @@ cfg_client_type_ann("RegisterRestClient"). // MicroProfile
// ── (10) PERSISTENCE: a query is a reference to a schema ────────────────────
cfg_entity_ann("Entity"). cfg_entity_ann("MappedSuperclass").
cfg_query_ann("NamedQuery"). cfg_query_ann("NamedNativeQuery").
+// the same declarations in a mapping file (META-INF/orm.xml): the element carries the name
+// attribute and a child holding the text.
+cfg_orm_named_query_elem("named-query"). cfg_orm_named_query_elem("named-native-query").
// the EntityManager-like receivers that make a call a query, for the same reason a
// broker send is gated on its template: `createQuery` is not a rare method name.
@@ -257,6 +362,13 @@ cfg_query_method("createNativeQuery", "inline").
// and there is no call site anywhere (Spring Data, and Micronaut Data's @Query).
cfg_method_query_ann("Query"). cfg_method_query_ann("NativeQuery").
cfg_query_arg("value"). cfg_query_arg("query").
+// the subject keywords a Spring Data DERIVED query method name opens with; the criteria
+// follow the first `By` after it (framework-behavior/persistence.dl).
+cfg_derived_query_prefix("find"). cfg_derived_query_prefix("read").
+cfg_derived_query_prefix("get"). cfg_derived_query_prefix("query").
+cfg_derived_query_prefix("search"). cfg_derived_query_prefix("stream").
+cfg_derived_query_prefix("count"). cfg_derived_query_prefix("exists").
+cfg_derived_query_prefix("delete"). cfg_derived_query_prefix("remove").
// ── (15) CONDITIONS: an annotation that decides whether a bean EXISTS ────────
// A bean the container did not create has no methods that run, so a condition is
// the difference between a live filter chain and a dead one. Three families, kept
@@ -401,3 +513,17 @@ cfg_entity_callback_ann("PreUpdate", "update"). cfg_entity_callback_ann("Post
cfg_entity_callback_ann("PreRemove", "remove"). cfg_entity_callback_ann("PostRemove", "remove").
// a type annotation whose class arguments are listener classes
cfg_entity_listeners_ann("EntityListeners").
+
+// ── (22) EXCEPTION HANDLERS: a built exception runs the @ExceptionHandler for it ──
+// A web framework hands an exception that leaves a request handler to the handler
+// method declared for its type: one in the same controller, or one in an advice class
+// for every controller. No call site names the handler
+// (call-edge-generation/exception_handlers.dl).
+//
+// cfg_exh_ann(Name): a method annotation that makes the method an exception handler
+// for the types named in cfg_exh_arg, or, with none named, for its parameter's type.
+cfg_exh_ann("ExceptionHandler").
+cfg_exh_arg("value"). cfg_exh_arg("exception").
+// cfg_exh_advice_ann(Name): a type annotation whose handlers serve every controller.
+cfg_exh_advice_ann("ControllerAdvice").
+cfg_exh_advice_ann("RestControllerAdvice").
diff --git a/graph/java/engine/config-resolution/xml-wiring.dl b/graph/java/engine/config-resolution/xml-wiring.dl
index b24da4a8..53223a2b 100644
--- a/graph/java/engine/config-resolution/xml-wiring.dl
+++ b/graph/java/engine/config-resolution/xml-wiring.dl
@@ -103,8 +103,15 @@ xml_prop_value_key(ownerElem, propName, key, default) :-
cfg_placeholder(v, key, default).
// A SpEL expression in XML is a declared blind spot: `#{...}` can name anything.
+// Not in a document whose ROOT element says `#{...}` means something else: in a MyBatis
+// , `#{id}` is a bound parameter, a property of the statement's parameter object,
+// and nothing Spring evaluates (cfg_xml_param_marker_root). The parser classifies every
+// `#{...}` as SPEL_EXPRESSION whatever the document, and on a mapper-heavy project those
+// rows outnumbered every real unknown.
+xml_param_marker_file(f) :- xml_elem("client", tag, _, _, "", f, _), cfg_xml_param_marker_root(tag).
config_unresolved("xml", vh, expr, "spel_expression") :-
- xml_vref("client", expr, "SPEL_EXPRESSION", _, _, _, vh).
+ xml_vref("client", expr, "SPEL_EXPRESSION", _, owner, _, vh),
+ xml_elem("client", _, _, _, _, f, owner), !xml_param_marker_file(f).
// ─────────────────────────────────────────────────────────────────────────────
// 4. CONTAINER REGISTRATIONS (web.xml and friends)
diff --git a/graph/java/engine/expression-resolution/expr-type.dl b/graph/java/engine/expression-resolution/expr-type.dl
index 94366240..054c50df 100644
--- a/graph/java/engine/expression-resolution/expr-type.dl
+++ b/graph/java/engine/expression-resolution/expr-type.dl
@@ -649,16 +649,10 @@ recv_decl_ref(recv, retRef, recvType) :- applicable_candidate(recv, innerCallee)
// declared-type clause above yields a reference carrying no arguments. The initializer's callee
// return type carries them. The field analogue is unnecessary: Java has no `var` fields, so a
// field always writes its type.
-recv_decl_ref(recv, retRef, t) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, recv),
- expr_ultimate_method("client", recv, m),
- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local),
- java_expression("METHOD_INVOCATION", _, "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, initExpr),
+recv_decl_ref(recv, retRef, t) :- local_use_init(recv, initExpr),
applicable_candidate(initExpr, callee),
method_return_type_resolves(_, callee, retRef, t).
-recv_decl_ref(recv, retRef, t) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, recv),
- expr_ultimate_method("client", recv, m),
- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local),
- java_expression("METHOD_INVOCATION", _, "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, initExpr),
+recv_decl_ref(recv, retRef, t) :- local_use_init(recv, initExpr),
applicable_candidate(initExpr, callee),
lib_type_reference(_, "METHOD_RETURN", _, _, _, _, _, "0", _, _, _, _, _, _, _, callee, "METHOD", retRef),
lib_type_ref_resolves(_, retRef, t).
diff --git a/graph/java/engine/expression-resolution/overload.dl b/graph/java/engine/expression-resolution/overload.dl
index 0e918839..e312a933 100644
--- a/graph/java/engine/expression-resolution/overload.dl
+++ b/graph/java/engine/expression-resolution/overload.dl
@@ -299,6 +299,41 @@ arg_value_type(call, pos, vt) :- java_expression(_, "ARGUMENT", _, _, _, _, call
arg_value_type(call, pos, vt) :- java_expression(_, "ARGUMENT", _, _, _, _, call, pos, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, arg),
expr_ultimate_type("client", arg, encl),
java_field(name, vt, _, _, _, _, _, _, encl, _, _, _, _, _), value_type_name(vt).
+// An ARITHMETIC or COMPARISON expression argument: `new Elements(elements.size() - 1)`, `remove(--i)`, `put(k, a == b)`.
+// Its operands may be untyped (a call into an unstaged library), but the operator fixes what kind of value it is:
+// `-` `*` `/` `%` and the shifts make a number, a comparison or `&&` / `||` / `!` a boolean. `+` is left out (a String
+// concatenation is a `+` too), and so are `&` `|` `^` (numeric or boolean by operand). "numeric" is a value type of its
+// own because the width is not known: it is compatible with every numeric primitive and wrapper, never with String,
+// boolean or a reference type. Without it the call kept `Elements(Element...)` beside `Elements(int)`, and every
+// caller of the one was a caller of the other.
+// java_expression (25): 1 kind 2 edgeRole 7 parentExpressionHash 8 position 14 operatorString
+.decl numeric_op(o:symbol)
+.decl numeric_unary_op(o:symbol)
+.decl boolean_op(o:symbol)
+.decl numeric_prim(p:symbol)
+.decl prim_value(v:symbol)
+numeric_op("-"). numeric_op("*"). numeric_op("/"). numeric_op("%"). numeric_op("<<"). numeric_op(">>"). numeric_op(">>>").
+numeric_unary_op("-"). numeric_unary_op("+"). numeric_unary_op("++"). numeric_unary_op("--"). numeric_unary_op("~").
+boolean_op("=="). boolean_op("!="). boolean_op("<"). boolean_op(">"). boolean_op("<="). boolean_op(">="). boolean_op("&&"). boolean_op("||").
+arg_value_type(call, pos, "numeric") :- java_expression("BINARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, op, _, _, _, _, _, _, _, _, _, _, _),
+ numeric_op(op).
+arg_value_type(call, pos, "numeric") :- java_expression("UNARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, op, _, _, _, _, _, _, _, _, _, _, _),
+ numeric_unary_op(op).
+arg_value_type(call, pos, "boolean") :- java_expression("BINARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, op, _, _, _, _, _, _, _, _, _, _, _),
+ boolean_op(op).
+arg_value_type(call, pos, "boolean") :- java_expression("UNARY_EXPRESSION", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, "!", _, _, _, _, _, _, _, _, _, _, _).
+numeric_prim("int"). numeric_prim("long"). numeric_prim("float"). numeric_prim("double").
+numeric_prim("short"). numeric_prim("byte"). numeric_prim("char").
+value_compatible("numeric", b) :- value_prim(b, p), numeric_prim(p).
+// A primitive value (a literal, a primitive variable, an arithmetic or comparison expression) never reaches a
+// parameter typed as a CLIENT type: boxing makes a java.lang wrapper, which no project type is.
+prim_value(v) :- value_prim(v, v), v != "String", v != "null".
+prim_value("numeric").
+value_mismatch(call, callee) :- expr_call_candidate(call, callee),
+ arg_value_type(call, pos, at), prim_value(at),
+ param_type_hash(callee, pos, pt), type_prov(pt, "client"),
+ !arg_is_target_typed(call, pos).
+
// Parameter value-type (parameterTypeName col 4 is the simple name: int / String / ...).
param_value_type(callee, pos, vt) :- java_method_parameter(_, pos, callee, _, vt, _, _, _, _, _, _, _, _), value_type_name(vt).
param_value_type(callee, pos, vt) :- lib_method_parameter(_, pos, callee, _, vt, _, _, _, _, _, _, _, _), value_type_name(vt).
diff --git a/graph/java/engine/framework-behavior/destinations.dl b/graph/java/engine/framework-behavior/destinations.dl
index 3c21a940..a91ba983 100644
--- a/graph/java/engine/framework-behavior/destinations.dl
+++ b/graph/java/engine/framework-behavior/destinations.dl
@@ -117,13 +117,13 @@ route_tail_declared(a) :- ann_on_method(_, a, n, _, _), cfg_route_ann(n), cfg_ro
// then answers "who serves this route" with somebody else's outbound call.
route_type_is_client(t) :- ann_on_type(_, _, n, t), cfg_client_type_ann(n).
-route_raw_of_method(m, path, "server") :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
+route_raw_own(m, path, "server") :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
cfg_route_ann(n), cfg_route_server_ann(n), !route_type_is_client(owner),
- route_base(owner, base), path = cat(base, tail).
-route_raw_of_method(m, path, "client") :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
- cfg_route_ann(n), !cfg_route_server_ann(n), route_base(owner, base), path = cat(base, tail).
-route_raw_of_method(m, path, "client") :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
- cfg_route_ann(n), route_type_is_client(owner), route_base(owner, base), path = cat(base, tail).
+ route_base(owner, base), path_join(base, tail, path).
+route_raw_own(m, path, "client") :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
+ cfg_route_ann(n), !cfg_route_server_ann(n), route_base(owner, base), path_join(base, tail, path).
+route_raw_own(m, path, "client") :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
+ cfg_route_ann(n), route_type_is_client(owner), route_base(owner, base), path_join(base, tail, path).
// A handler that names only its VERB still has a path: its resource class's. The
// verb annotation contributes no remainder of its own, so the route is the base and
// nothing more, which is what JAX-RS means by @GET on a method under @Path("/orders").
@@ -131,13 +131,91 @@ route_raw_of_method(m, path, "client") :- route_tail(m, tail), ann_on_method(_,
// its remainder from the @Path, and without the guard it would ALSO produce a second,
// shorter route at the bare class path.
method_route_ann(m) :- ann_on_method(_, _, n, m, _), cfg_route_ann(n).
-route_raw_of_method(m, base, "server") :- ann_on_method(_, _, n, m, owner),
+route_raw_own(m, base, "server") :- ann_on_method(_, _, n, m, owner),
cfg_route_verb_ann(n, _), !method_route_ann(m),
!route_type_is_client(owner), route_base(owner, base).
// … and the same shape on a declarative client interface (MicroProfile @RegisterRestClient).
-route_raw_of_method(m, base, "client") :- ann_on_method(_, _, n, m, owner),
+route_raw_own(m, base, "client") :- ann_on_method(_, _, n, m, owner),
cfg_route_verb_ann(n, _), !method_route_ann(m),
route_type_is_client(owner), route_base(owner, base).
+route_raw_of_method(m, p, side) :- route_raw_own(m, p, side), method_owner("client", owner, m),
+ !locator_only_resource(owner).
+
+// ── joining the two halves: exactly one '/' between them ────────────────────
+// JAX-RS and Spring both insert the separator when they concatenate a class path and a
+// method path, and drop a doubled one: @Path("/items") + @Path("{id}") is /items/{id},
+// and @RequestMapping("/api/") + @GetMapping("/x") is /api/x. A bare cat() gave
+// /items{id} and /api//x, neither of which any client writes.
+path_join_demand(base, tail) :- route_tail(m, tail), ann_on_method(_, _, n, m, owner),
+ cfg_route_ann(n), route_base(owner, base).
+path_join(b, t, t) :- path_join_demand(b, t), b = "".
+path_join(b, t, b) :- path_join_demand(b, t), b != "", t = "".
+path_join(b, t, j) :- path_join_demand(b, t), path_ends_slash(b), path_starts_slash(t),
+ j = cat(b, substr(t, 1, strlen(t) - 1)).
+path_join(b, t, j) :- path_join_demand(b, t), path_ends_slash(b), path_starts_other(t),
+ j = cat(b, t).
+path_join(b, t, j) :- path_join_demand(b, t), path_ends_other(b), path_starts_slash(t),
+ j = cat(b, t).
+path_join(b, t, j) :- path_join_demand(b, t), path_ends_other(b), path_starts_other(t),
+ j = cat(b, cat("/", t)).
+path_ends_slash(b) :- path_join_demand(b, _), strlen(b) > 0, substr(b, strlen(b) - 1, 1) = "/".
+path_starts_slash(t) :- path_join_demand(_, t), strlen(t) > 0, substr(t, 0, 1) = "/".
+// the negative halves are stated positively: the locator rules feed path_join_demand from
+// a route that path_join itself produced, so a negation here would be a cycle through it
+path_ends_other(b) :- path_join_demand(b, _), strlen(b) > 0, substr(b, strlen(b) - 1, 1) != "/".
+path_starts_other(t) :- path_join_demand(_, t), strlen(t) > 0, substr(t, 0, 1) != "/".
+
+// ── JAX-RS SUB-RESOURCE LOCATORS ────────────────────────────────────────────
+// A method with @Path and no verb returns the object that serves the rest of the request:
+// @Path("/widgets") class Widgets { @Path("/{id}") Widget find(..) { .. } }
+// class Widget { @GET String details() { .. } }
+// Widget.details serves GET /widgets/{id}. The locator's declared return type is the
+// resource; its verb methods take the locator's route, and its own @Path methods extend
+// it. One level only: a resource that returns its own type would otherwise grow the
+// route without end.
+jaxrs_locator(m, r) :- ann_on_method(_, _, n, m, owner), cfg_subresource_locator_ann(n),
+ !method_verb_declared(m), !route_type_is_client(owner),
+ method_return_type_resolves("client", m, _, r), r != owner.
+locator_base(r, raw) :- jaxrs_locator(m, r), route_raw_own(m, raw, "server").
+// A class with no @Path of its own that a locator returns is not a root resource: its
+// methods are served only below the locator, never at their own bare path.
+locator_only_resource(r) :- jaxrs_locator(_, r), !route_base_declared(r).
+route_raw_of_method(m, raw, "server") :- locator_base(r, raw), ann_on_method(_, _, n, m, r),
+ cfg_route_verb_ann(n, _), !method_route_ann(m).
+route_raw_of_method(m, p, "server") :- locator_base(r, raw), route_tail(m, tail),
+ ann_on_method(_, _, n, m, r), cfg_route_ann(n), cfg_route_server_ann(n), path_join(raw, tail, p).
+path_join_demand(raw, tail) :- locator_base(r, raw), route_tail(m, tail), ann_on_method(_, _, _, m, r).
+
+// ── SERVLETS MAPPED TO A URL PATTERN ────────────────────────────────────────
+// web.xml maps a servlet to its patterns through the both elements carry:
+// wapp.W
+// w/widgets
+// and @WebServlet("/widgets") / urlPatterns = {..} says the same on the class. The servlet's
+// do* methods serve the pattern, each for its own verb.
+servlet_decl_name(t, name, fp) :- xml_container_class("web_servlet", t, elem),
+ xml_elem("client", _, _, _, parent, fp, elem),
+ xml_elem("client", nt, _, raw, parent, _, _), cfg_xml_servlet_name_elem(nt),
+ cfg_clean(raw, name), name != "".
+servlet_mapped_pattern(name, pat, fp) :- xml_elem("client", mt, _, _, _, fp, mp), cfg_xml_servlet_mapping_elem(mt),
+ xml_elem("client", nt, _, rawn, mp, _, _), cfg_xml_servlet_name_elem(nt), cfg_clean(rawn, name),
+ xml_elem("client", pt, _, rawp, mp, _, _), cfg_xml_url_pattern_elem(pt), cfg_clean(rawp, pat).
+servlet_pattern(t, pat) :- servlet_decl_name(t, name, fp), servlet_mapped_pattern(name, pat, fp).
+servlet_pattern(t, pat) :- ann_on_type("client", a, "WebServlet", t), cfg_servlet_url_arg(arg),
+ ann_arg("client", a, arg, pat, "STRING_LITERAL", _).
+// An exact pattern is the route. A path-prefix pattern "/widgets/*" serves every path
+// below /widgets, which the join can only express as a template: /widgets/{path} meets a
+// client that concatenates "/widgets/" + id by route shape, never exactly. The catch-all
+// "/*", the default servlet "/" and an extension pattern "*.do" name no route.
+servlet_route_raw(t, pat) :- servlet_pattern(t, pat), !contains("*", pat), pat != "/", pat != "".
+servlet_route_raw(t, r) :- servlet_pattern(t, pat), strlen(pat) > 2,
+ substr(pat, strlen(pat) - 2, 2) = "/*", pre = substr(pat, 0, strlen(pat) - 2),
+ !contains("*", pre), r = cat(pre, "/{path}").
+servlet_handler(m, t, v) :- servlet_route_raw(t, _),
+ java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
+ cfg_servlet_verb_method(name, v).
+route_declared(raw) :- servlet_route_raw(_, raw).
+serves_destination(m, "http", u) :- servlet_handler(m, t, _), servlet_route_raw(t, raw), url_path(raw, u).
+
route_declared(raw) :- route_raw_of_method(_, raw, _).
// A declared route goes through the same normalisation as a client's URL, so
// @GetMapping("pets/visits") and a client's "…/pets/visits?petId={id}" meet.
@@ -232,7 +310,13 @@ sends_destination_at(call, m, tr, d) :- sends_via(call, tr), call_site(call, nam
// (d) an imperative HTTP call. The URL is routinely built by concatenation, so the
// destination is any path-shaped string written anywhere inside the URL argument.
-expr_descendant(a, a) :- sends_via(c, "http"), call_arg(c, _, a).
+// Only the URL argument. A RestTemplate send takes (url, request body, ..): reading every
+// argument made a body literal ("shipped") a destination of its own, which linked the
+// call to whatever handler served /shipped and, as a second fragment, cost it the route
+// it really calls. Every method in cfg_sends_http takes the URL first.
+http_url_arg(c, a) :- sends_via(c, "http"), call_site(c, name, _), cfg_sends_http(name),
+ call_arg(c, "0", a).
+expr_descendant(a, a) :- http_url_arg(_, a).
expr_descendant(a, d) :- expr_descendant(a, c), expr_child("client", c, _, d).
// Real code builds the URL into a local and passes the local:
// String url = base + "/api/x/" + id + "/detail";
@@ -244,8 +328,7 @@ expr_descendant(a, d) :- expr_descendant(a, c), expr_reference_name(c, n),
java_local_variable(n, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, lv),
expr_owner("client", lv, _, d).
-http_arg_literal(v) :- sends_via(call, "http"), call_site(call, name, _), cfg_sends_http(name),
- call_arg(call, _, arg), expr_descendant(arg, d), expr_kind("client", "LITERAL", _, d),
+http_arg_literal(v) :- http_url_arg(_, arg), expr_descendant(arg, d), expr_kind("client", "LITERAL", _, d),
expr_literal("client", v, d).
// A URL literal is a destination in its own right. Without this the only client paths
// that resolved were the ones that happened to be spelled identically to a declared
@@ -295,15 +378,43 @@ url_path(s, u) :- url_no_scheme(s, p), url_no_query(p, q), strlen(q) > 0,
url_path(s, u) :- url_no_scheme(s, p), url_no_query(p, q), strlen(q) > 0,
substr(q, 0, 1) != "/", u = cat("/", q).
-sends_destination_at(call, m, "http", u) :- sends_via(call, "http"), call_site(call, name, _),
- cfg_sends_http(name), call_arg(call, _, arg), expr_descendant(arg, d),
+sends_destination_at(call, m, "http", u) :- http_url_arg(call, arg), expr_descendant(arg, d),
arg_destination(d, p), url_path(p, u), expr_ultimate_method("client", call, m).
sends_destination(m, tr, u) :- sends_destination_at(_, m, tr, u).
+// (e) the JAX-RS client: client.target("http://shop/orders").path("{id}").request().get(..).
+// The URL is built across the chain, so it is followed from target(..) through each
+// path(..) to request(..) and the verb that sends it. Kept apart from sends_via: the
+// verb methods (put, delete) share names with RestTemplate's URL-first sends, and
+// reading put(Entity.json(x))'s argument as a URL is the defect (d) above avoids.
+// The chain root is declared as the JAX-RS Client / WebTarget, and that name does not resolve
+// to a class of the project (a project's own `Client` with a target(..) method is not the
+// API), or is typed as the library's; or the chain starts on ClientBuilder itself.
+jaxrs_client_root(root) :- recv_type_simple(root, n), cfg_jaxrs_client_owner(n).
+jaxrs_client_root(root) :- recv_declared_simple(root, n), cfg_jaxrs_client_owner(n),
+ !expr_type("client", root, _).
+jaxrs_client_root(root) :- expr_reference_name(root, n), cfg_jaxrs_client_factory(n),
+ !simple_name_declared(n).
+jaxrs_target(call, d) :- call_site(call, "target", recv), recv_root(recv, root),
+ jaxrs_client_root(root), call_arg(call, "0", arg), arg_destination(arg, d).
+jaxrs_target(call, j) :- call_site(call, "path", recv), jaxrs_target(recv, base),
+ call_arg(call, "0", arg), expr_kind("client", "LITERAL", _, arg),
+ expr_literal("client", v, arg), path_join(base, v, j).
+path_join_demand(base, v) :- call_site(call, "path", recv), jaxrs_target(recv, base),
+ call_arg(call, "0", arg), expr_kind("client", "LITERAL", _, arg), expr_literal("client", v, arg).
+dest_raw(v) :- call_site(call, "target", _), call_arg(call, "0", arg),
+ expr_kind("client", "LITERAL", _, arg), expr_literal("client", v, arg).
+jaxrs_builder(call, d) :- call_site(call, "request", tgt), jaxrs_target(tgt, d).
+jaxrs_builder(call, d) :- call_site(call, n, inner), cfg_jaxrs_builder_step(n), jaxrs_builder(inner, d).
+jaxrs_send(call, m, d, v) :- call_site(call, n, b), cfg_jaxrs_invoke_verb(n, v), jaxrs_builder(b, d),
+ expr_ultimate_method("client", call, m).
+path_candidate(d) :- jaxrs_send(_, _, d, _).
+sends_destination_at(call, m, "http", u) :- jaxrs_send(call, m, d, _), url_path(d, u).
+
// EVERY path fragment this one call writes. A URL built by concatenation leaves more
// than one — "/api/inventory/" + sku + "/reserved" leaves two — and a prefix rule that
// looks at only the first will answer the route that the second one rules out.
-send_fragment(call, f) :- sends_via(call, "http"), call_arg(call, _, arg),
+send_fragment(call, f) :- http_url_arg(call, arg),
expr_descendant(arg, d), expr_kind("client", "LITERAL", _, d),
expr_literal("client", raw, d), url_path(raw, f).
// NOTE: deliberately NOT gated on http_path_shaped. That guard wants two segments,
@@ -321,7 +432,7 @@ route_contradicted(call, s) :- send_fragment(call, f), serves_destination(_, "ht
// where they are written, the FIRST is the route's literal prefix and the LAST is its
// tail, and the segment counts must add up — which is what keeps
// /api/x/{id}/reserve away from /api/x/{id}/reservation.
-send_frag_at(call, f, key) :- sends_via(call, "http"), call_arg(call, _, arg),
+send_frag_at(call, f, key) :- http_url_arg(call, arg),
expr_descendant(arg, d), expr_kind("client", "LITERAL", _, d),
expr_literal("client", raw, d), url_path(raw, f),
expr_src_pos("client", d, line, col),
@@ -428,6 +539,8 @@ method_verb(m, v) :- ann_on_method(_, _, n, m, _), cfg_route_verb_ann(n, v).
method_verb_declared(m) :- ann_on_method(_, _, n, m, _), cfg_route_verb_ann(n, _).
method_verb(m, "any") :- ann_on_method(_, _, n, m, _), cfg_route_ann(n), !cfg_route_verb_known(n), !method_verb_declared(m).
cfg_route_verb_known(n) :- cfg_route_verb(n, _).
+// a servlet's doGet serves GET, its service(..) every verb
+method_verb(m, v) :- servlet_handler(m, _, v).
// … the client's from the call it made, or from its own exchange annotation.
call_verb(call, v) :- call_site(call, name, _), cfg_sends_verb(name, v).
// a fluent chain names the verb one call earlier: .get().uri(path)
@@ -436,6 +549,8 @@ call_verb(call, v) :- call_site(call, _, recv), call_site(recv, name, _), cfg_se
send_verb(m, v) :- sends_via(call, "http"), expr_ultimate_method("client", call, m), call_verb(call, v).
send_verb(m, "any") :- sends_destination(m, "http", _), !send_verb_known(m).
send_verb_known(m) :- sends_via(call, "http"), expr_ultimate_method("client", call, m), call_verb(call, _).
+send_verb(m, v) :- jaxrs_send(_, m, _, v).
+send_verb_known(m) :- jaxrs_send(_, m, _, _).
send_verb(m, v) :- route_of_method(m, _, "client"), method_verb(m, v).
// ── gRPC: the generated stub and the handler it serves (#1108) ──────────────────────────────
@@ -484,6 +599,89 @@ remote_edge_via(call, from, to, "grpc", key, conf) :- grpc_sends_at(call, from,
// the stub's type resolved, so the pairing rests on a type rather than a name alone
conf = "exact".
+// ── gRPC when the generated Grpc holder is NOT in the source tree (#1548) ────────────────
+// protoc usually runs at build time, into target/ or build/, so neither the ImplBase nor the stub
+// has a java_type row and the rules above never fire. Both ends are still EXTERNAL types
+// (resolution/external-types.dl) whose names carry the holder as written: `extends
+// OrderServiceGrpc.OrderServiceImplBase` and a field of type `OrderServiceGrpc.OrderServiceBlockingStub`.
+// These clauses feed grpc_serves and grpc_sends_at, so the edge rule above is shared.
+//
+// The shape is checked, not just the suffix: the holder is `Grpc` and the nested type is ``
+// plus a server or client suffix, the same on both. An external `RequestStub` or `Foo.BarStub`
+// is not a generated stub.
+//
+// The key is the QUALIFIED holder, read per file. The external node is shared by every file that
+// wrote the same name, and one real 2,800-file project imports `MetricsServiceGrpc` from three
+// packages (two versions of one API and an unrelated one): keyed on the simple name, each client
+// would be paired with all three services. A written package qualifier, then the file's
+// single-type import of the holder, then the file's own package when it has no on-demand import,
+// which is the order javac resolves the name in. A holder only an on-demand import could supply
+// is left unpaired.
+grpc_ext_name_demand(x, w) :- external_type(x), strlen(x) > 9,
+ w = substr(x, 9, strlen(x) - 9), contains(".", w),
+ grpc_generated_suffix(k), strlen(w) > strlen(k),
+ substr(w, max(0, strlen(w) - strlen(k)), min(strlen(k), strlen(w))) = k.
+grpc_generated_suffix(k) :- cfg_stub_client_suffix(k).
+grpc_generated_suffix(k) :- cfg_stub_base_suffix(k, "grpc_service"), !cfg_grpc_holder_suffix(k).
+grpc_ext_dot(w, i) :- grpc_ext_name_demand(_, w), i = range(0, strlen(w)), substr(w, i, 1) = ".".
+grpc_ext_last_dot(w, l) :- grpc_ext_dot(w, _), l = max i : grpc_ext_dot(w, i).
+grpc_ext_prev_dot(w, p) :- grpc_ext_last_dot(w, l), grpc_ext_dot(w, j), j < l,
+ p = max i : { grpc_ext_dot(w, i), i < l }.
+grpc_ext_has_prev(w) :- grpc_ext_prev_dot(w, _).
+// grpc_ext_parts(ExternalType, Qualifier, Holder, Nested): `a.b.FooGrpc.FooStub` -> a.b, FooGrpc, FooStub
+grpc_ext_parts(x, pre, h, n) :- grpc_ext_name_demand(x, w), grpc_ext_prev_dot(w, p), grpc_ext_last_dot(w, l),
+ pre = substr(w, 0, p), h = substr(w, p + 1, l - p - 1), n = substr(w, l + 1, strlen(w) - l - 1).
+grpc_ext_parts(x, "", h, n) :- grpc_ext_name_demand(x, w), !grpc_ext_has_prev(w), grpc_ext_last_dot(w, l),
+ h = substr(w, 0, l), n = substr(w, l + 1, strlen(w) - l - 1).
+grpc_ext_service(x, s, n) :- grpc_ext_parts(x, _, h, n), cfg_grpc_holder_suffix(hs),
+ strlen(h) > strlen(hs), substr(h, max(0, strlen(h) - strlen(hs)), min(strlen(hs), strlen(h))) = hs,
+ s = substr(h, 0, max(0, strlen(h) - strlen(hs))).
+grpc_ext_server(x) :- grpc_ext_service(x, s, n), cfg_stub_base_suffix(k, "grpc_service"), n = cat(s, k).
+grpc_ext_client(x) :- grpc_ext_service(x, s, n), cfg_stub_client_suffix(k), n = cat(s, k).
+
+// the service class that extends the external ImplBase, and the file that wrote the extends
+grpc_ext_impl_site(t, x, fp) :-
+ java_type_reference(_, "SUPER_TYPE", t, _, _, _, _, "0", _, _, _, _, _, _, _, _, _, ref),
+ type_ref_external(ref, x), grpc_ext_server(x),
+ java_type(_, _, _, _, _, _, _, fp, _, _, _, _, _, t).
+// a call on a receiver typed as an external stub, and the file of the method that makes it
+grpc_ext_send_site(call, from, x, fp, name) :- call_site(call, name, recv), name != "",
+ expr_type("external", recv, x), grpc_ext_client(x),
+ expr_ultimate_method("client", call, from),
+ java_method(_, _, _, _, fp, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, from).
+
+grpc_ext_site(x, fp) :- grpc_ext_impl_site(_, x, fp).
+grpc_ext_site(x, fp) :- grpc_ext_send_site(_, _, x, fp, _).
+grpc_file_on_demand(fp) :- grpc_ext_site(_, fp), java_import(_, _, _, _, fp, _, "false", "true", _, _, _).
+grpc_file_package(fp, pkg) :- grpc_ext_site(_, fp),
+ java_type(n, q, _, _, _, _, "TOP_LEVEL_PLACEMENT", fp, _, _, _, _, _, _),
+ strlen(q) > strlen(n), pkg = substr(q, 0, max(0, strlen(q) - strlen(n) - 1)).
+grpc_file_package(fp, "") :- grpc_ext_site(_, fp),
+ java_type(n, n, _, _, _, _, "TOP_LEVEL_PLACEMENT", fp, _, _, _, _, _, _).
+// grpc_ext_holder_q(ExternalType, File, QualifiedHolder)
+grpc_ext_holder_q(x, fp, q) :- grpc_ext_site(x, fp), grpc_ext_parts(x, pre, h, _), pre != "",
+ q = cat(pre, cat(".", h)).
+grpc_ext_holder_q(x, fp, q) :- grpc_ext_site(x, fp), grpc_ext_parts(x, "", h, _),
+ java_import("SINGLE_TYPE", q, _, h, fp, _, "false", _, _, _, _).
+grpc_ext_holder_q(x, fp, q) :- grpc_ext_site(x, fp), grpc_ext_parts(x, "", h, _),
+ !type_has_explicit_import(h, fp), !grpc_file_on_demand(fp),
+ grpc_file_package(fp, pkg), pkg != "", q = cat(pkg, cat(".", h)).
+grpc_ext_holder_q(x, fp, h) :- grpc_ext_site(x, fp), grpc_ext_parts(x, "", h, _),
+ !type_has_explicit_import(h, fp), !grpc_file_on_demand(fp), grpc_file_package(fp, "").
+
+// An rpc method takes the response StreamObserver: (request, observer) for unary and server
+// streaming, (observer) for client and bidi streaming. With the ImplBase absent there is no
+// declaration to test an override against, and a helper written beside the rpcs has no observer.
+grpc_rpc_shaped(m) :- java_method_parameter(_, _, m, bt, _, _, _, _, _, _, _, _, _), cfg_grpc_observer_type(bt).
+
+grpc_ext_handler(t, x, fp) :- grpc_ext_impl_site(t, x, fp).
+grpc_ext_handler(t, x, fp) :- grpc_ext_impl_site(t0, x, fp), type_ancestor(t, t0).
+grpc_serves(m, key) :- grpc_ext_handler(t, x, fp), grpc_ext_holder_q(x, fp, q),
+ java_method(name, _, _, _, _, _, _, t, _, _, _, _, _, _, _, _, "INSTANCE_METHOD", _, _, _, _, m),
+ name != "", grpc_rpc_shaped(m), key = cat(q, cat("/", name)).
+grpc_sends_at(call, from, key) :- grpc_ext_send_site(call, from, x, fp, name),
+ grpc_ext_holder_q(x, fp, q), key = cat(q, cat("/", name)).
+
// (a) exact — a topic, a queue, or a route both ends spell the same way.
remote_edge_via(call, from, to, tr, d, "exact") :- sends_destination_at(call, from, tr, d),
serves_destination(to, tr, d), from != to, tr != "http".
diff --git a/graph/java/engine/framework-behavior/persistence.dl b/graph/java/engine/framework-behavior/persistence.dl
index ae33a78d..6ffd4dd1 100644
--- a/graph/java/engine/framework-behavior/persistence.dl
+++ b/graph/java/engine/framework-behavior/persistence.dl
@@ -47,6 +47,17 @@ jpa_entity_field(t, fname, f) :- jpa_entity(t), type_ancestor(t, sup),
// type with its own arguments, so no special handling of the container is needed.
jpa_named_query(name, text, t) :- ann_on_type("client", a, n, t), cfg_query_ann(n),
ann_arg("client", a, "name", name, _, _), ann_arg("client", a, "query", text, _, _).
+// … and in a mapping file: `…` in
+// META-INF/orm.xml (or any mapping file the unit lists). The parser already holds the
+// element, its name attribute and the child's text; the declaring "type" is the element,
+// since a mapping-file query belongs to the persistence unit rather than to a class. The
+// JPQL is the child's text exactly as it is for the annotation's `query` argument, so the
+// entity and property rules below read both the same way. A is SQL,
+// which names tables rather than entities, and is left to report no_entity_in_query.
+orm_named_query(name, text, nq) :- xml_elem("client", tag, _, _, _, _, nq), cfg_orm_named_query_elem(tag),
+ xml_attr("client", "name", raw, nq, _), cfg_clean(raw, name), name != "",
+ xml_elem("client", "query", _, text, nq, _, _), text != "".
+jpa_named_query(name, text, nq) :- orm_named_query(name, text, nq).
// ── uses: em.createNamedQuery("…") / em.createQuery("…") ────────────────────
// Gated on the receiver's declared type, for the same reason a broker send is: the
@@ -99,6 +110,75 @@ persistence_query(m, "inline", "-", text) :- query_call(_, m, "inline", text).
persistence_query(m, "spring_data", "-", text) :- ann_on_method("client", a, n, m, _),
cfg_method_query_ann(n), cfg_query_arg(arg), ann_arg("client", a, arg, text, _, _).
+// ── Spring Data: a DERIVED query, where the method NAME is the query ────────
+// `List findByColorAndLabelStartingWith(String c, String p)` on an interface
+// extending a repository base carries no text at all: the framework parses the name at
+// start-up, and a property the name spells that the entity does not have fails there.
+// So renaming Widget.color breaks findByColor exactly as it breaks an @Query naming
+// `w.color`, and it is recorded the same way: persistence_query (kind "derived", the
+// method name as its text), persistence_entity, and one persistence_field per property.
+//
+// The entity is the repository base's FIRST type argument (`JpaRepository`),
+// read from the written extends clause because the base is normally not staged; the
+// base is recognised by the same suffix knob that makes the interface a proxy bean
+// (cfg_repository_base_suffix, di.dl). The entity need not carry @Entity: a document
+// store's repository derives its queries the same way.
+//
+// The NAME: a subject prefix (cfg_derived_query_prefix: find, read, get, query, search,
+// stream, count, exists, delete, remove), anything up to the first `By` after it
+// (`Distinct`, `Top10`, `All`), then the criteria. A property is matched where a
+// criterion can start (right after that `By`, an `And`, an `Or` or an `OrderBy`) as
+// the capitalised property name, followed by the end of the name, `_` or another
+// capital (an operator such as `StartingWith`, the next `And`, a nested path). Where
+// two properties match at one place the LONGER one is the property (`labelText` over
+// `label`), as Spring's own parser resolves it. A method carrying @Query is not
+// derived: the annotation's text wins, and the rule above already reads it.
+repo_entity(repo, e) :- type_super_ref_h(repo, sn, sref), cfg_repo_name_matches(sn),
+ type_decl(_, _, _, _, "INTERFACE_TYPE", _, repo),
+ java_type_reference(_, _, _, _, _, sref, "0", "1", _, _, _, _, _, _, _, _, _, argRef),
+ type_ref_resolves(_, argRef, e), java_type(_, _, _, _, _, _, _, _, _, _, _, _, _, e).
+repo_entity_field(e, fname, f) :- repo_entity(_, e),
+ java_field(fname, _, _, _, _, _, _, _, e, _, _, _, _, f).
+repo_entity_field(e, fname, f) :- repo_entity(_, e), type_ancestor(e, sup),
+ java_field(fname, _, _, _, _, _, _, _, sup, _, _, _, _, f).
+// the capitalised property name, as it is spelled inside a method name
+repo_field_cap(e, cap, f) :- repo_entity_field(e, fname, f), strlen(fname) > 0,
+ cfg_upper(substr(fname, 0, 1), up), cap = cat(up, substr(fname, 1, strlen(fname) - 1)).
+repo_field_cap(e, fname, f) :- repo_entity_field(e, fname, f), strlen(fname) > 0,
+ cfg_is_upper_char(substr(fname, 0, 1)).
+
+has_method_query_ann(m) :- ann_on_method("client", _, n, m, _), cfg_method_query_ann(n).
+derived_candidate(m, name, e, p) :- repo_entity(repo, e),
+ java_method(name, _, _, _, _, _, _, repo, _, _, _, _, _, _, _, _, "ABSTRACT_METHOD", _, _, _, _, m),
+ cfg_derived_query_prefix(p), strlen(name) > strlen(p) + 2,
+ substr(name, 0, strlen(p)) = p, !has_method_query_ann(m).
+derived_by_at(m, i) :- derived_candidate(m, name, _, p),
+ i = range(strlen(p), strlen(name) - 1), substr(name, i, 2) = "By".
+derived_criteria(m, e, crit) :- derived_candidate(m, name, e, _),
+ b = min i : derived_by_at(m, i), b + 2 < strlen(name),
+ crit = substr(name, b + 2, strlen(name) - b - 2).
+// where a criterion may start: the beginning, or after And / Or / By (OrderBy), at a capital
+derived_start(m, 0) :- derived_criteria(m, _, _).
+derived_start(m, j) :- derived_criteria(m, _, crit), derived_joiner(w),
+ j = range(1, strlen(crit)), j >= strlen(w), substr(crit, j - strlen(w), strlen(w)) = w,
+ cfg_is_upper_char(substr(crit, j, 1)).
+derived_joiner("And"). derived_joiner("Or"). derived_joiner("By").
+// a property spelled at a start, and ending where a property name can end
+derived_match(m, j, n, f) :- derived_start(m, j), derived_criteria(m, e, crit),
+ repo_field_cap(e, cap, f), n = strlen(cap), j + n <= strlen(crit),
+ substr(crit, j, n) = cap, derived_ends(crit, j + n).
+derived_ends(crit, k) :- derived_criteria(_, _, crit), k = strlen(crit).
+derived_ends(crit, k) :- derived_criteria(_, _, crit), k = range(1, strlen(crit)),
+ cfg_is_upper_char(substr(crit, k, 1)).
+derived_ends(crit, k) :- derived_criteria(_, _, crit), k = range(1, strlen(crit)),
+ substr(crit, k, 1) = "_".
+derived_longest(m, j, n) :- derived_match(m, j, _, _), n = max l : derived_match(m, j, l, _).
+
+persistence_query(m, "derived", "-", name) :- derived_criteria(m, _, _),
+ derived_candidate(m, name, _, _).
+persistence_entity(m, e) :- derived_criteria(m, e, _).
+pf_derived(m, f) :- derived_longest(m, j, n), derived_match(m, j, n, f).
+
// ── what the query text names ───────────────────────────────────────────────
// No JPQL parser: an entity is recognised where a query can introduce one, and the
// candidate set is only the @Entity types, so the test is both cheap and specific.
@@ -108,7 +188,7 @@ persistence_query(m, "spring_data", "-", text) :- ann_on_method("client", a, n,
// start, in the middle and at the end. Both delimiters are required: measured on a real
// codebase, matching " FROM Pet" without the trailing space also matched
// " FROM PetType", and the blast radius of PetType.name then included Pet.name.
-query_padded(text, p) :- persistence_query(_, _, _, text), p = cat(" ", cat(text, " ")).
+query_padded(text, p) :- persistence_query(_, kind, _, text), kind != "derived", p = cat(" ", cat(text, " ")).
query_names_type(text, name) :- query_padded(text, p), jpa_entity(t),
java_type(name, _, _, _, _, _, _, _, _, _, _, _, _, t), jpa_intro(kw),
contains(cat(" ", cat(kw, cat(name, " "))), p).
@@ -117,7 +197,7 @@ jpa_intro("JOIN "). jpa_intro("join "). jpa_intro("Join ").
jpa_intro("UPDATE "). jpa_intro("update "). jpa_intro("Update ").
jpa_intro("INTO "). jpa_intro("into ").
-persistence_entity(m, t) :- persistence_query(m, _, _, text), query_names_type(text, name),
+persistence_entity(m, t) :- persistence_query(m, kind, _, text), kind != "derived", query_names_type(text, name),
java_type(name, _, _, _, _, _, _, _, _, _, _, _, _, t), jpa_entity(t).
// A property is named with a dot after its alias — `o.customerId`. The dot is what
@@ -141,6 +221,7 @@ pf_nested(m, f) :- persistence_entity(m, t), persistence_query(m, _, _, text),
mid != fname, jpa_after(d),
contains(cat(".", cat(mid, cat(".", cat(fname, d)))), p).
// A property read directly SOMEWHERE is direct, even if it also appears nested.
+pf_any(m, f) :- pf_derived(m, f).
pf_direct(m, f) :- pf_any(m, f), !pf_nested(m, f).
persistence_field(m, f, "direct") :- pf_direct(m, f).
persistence_field(m, f, "nested") :- pf_nested(m, f), !pf_direct(m, f).
diff --git a/graph/java/engine/resolution/external-types.dl b/graph/java/engine/resolution/external-types.dl
index c60de8a6..46427be8 100644
--- a/graph/java/engine/resolution/external-types.dl
+++ b/graph/java/engine/resolution/external-types.dl
@@ -78,6 +78,23 @@ name_resolves_in_file(tn, fp) :- ref_file(_, tn, fp), type_resolves_java_lang(_,
// on an inferred local got an `external:var.` boundary beside its real target (3,842 of 85,025
// edges on a 1,360-file project). Its type is the initializer's: local-flow.dl carries a client
// one, and the `var` clause of expr_type below an external one. #1401.
+//
+// A TYPE VARIABLE is not a type name either. A field written `protected M mapper` in `Base`
+// arrives as a CLASS reference named `M`, which nothing declares, so a call through it became a
+// library boundary on `external:M` (#1412). The variable is in scope from its declaring type, a
+// type enclosing that, or the generic method whose signature or body writes it.
+ref_names_tvar(ref) :- java_type_reference(_, _, encl, _, _, _, _, "0", tn, _, _, _, _, _, _, _, _, ref),
+ tn != "", java_type_parameter(tn, _, _, _, _, _, encl, _).
+ref_names_tvar(ref) :- java_type_reference(_, _, encl, _, _, _, _, "0", tn, _, _, _, _, _, _, _, _, ref),
+ tn != "", type_in_type("client", encl, outer), java_type_parameter(tn, _, _, _, _, _, outer, _).
+ref_names_tvar(ref) :- java_type_reference(_, _, _, _, _, _, _, "0", tn, _, _, _, _, _, _, m, "METHOD", ref),
+ tn != "", java_method_type_parameter(tn, _, _, _, _, _, _, m, _, _).
+ref_names_tvar(ref) :- java_type_reference(_, _, _, _, _, _, _, "0", tn, _, _, _, _, _, _, p, "METHOD_PARAM", ref),
+ tn != "", java_method_parameter(_, _, m, _, _, _, _, _, _, _, _, _, p),
+ java_method_type_parameter(tn, _, _, _, _, _, _, m, _, _).
+ref_names_tvar(ref) :- java_type_reference(_, _, _, _, _, _, _, "0", tn, _, _, _, _, _, _, l, "LOCAL_VARIABLE", ref),
+ tn != "", java_local_variable(_, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, l),
+ java_method_type_parameter(tn, _, _, _, _, _, _, m, _, _).
candidate_ref(ref, tn, ctn, fp) :- java_type_reference(kind, ctx, _, _, _, _, _, "0", tn, ctn, _, _, _, _, _, _, _, ref),
ext_ref_kind(kind),
ext_ref_context(ctx),
@@ -85,7 +102,8 @@ candidate_ref(ref, tn, ctn, fp) :- java_type_reference(kind, ctx, _, _, _, _, _,
tn != "var",
ref_file(ref, tn, fp),
!name_resolves_in_file(tn, fp),
- !simple_name_declared(tn).
+ !simple_name_declared(tn),
+ !ref_names_tvar(ref).
// ── ref_external_name(Ref, QualifiedName) ───────────────────────────────────
ref_external_name(ref, q) :- candidate_ref(ref, tn, _, fp),
@@ -133,7 +151,24 @@ local_creation_init(local) :- java_expression("ANONYMOUS_CLASS_CREATION", "ROOT"
local_reassigned(local) :- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local),
java_expression(_, "ASSIGNMENT_TARGET", _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, e),
expr_ultimate_method("client", e, m).
-local_creation_pinned(local) :- local_creation_init(local), !local_reassigned(local).
+local_creation_pinned(local) :- local_creation_init(local), !local_reassigned(local), !local_creation_external(local).
+// ...but a creation of a type no staged IR declares pins nothing: `final StringBuilder sb = new StringBuilder()`
+// with no JDK staged has no flow type at all, so `sb.toString()` was an unresolved site -- and every unresolved
+// `toString` / `append` was a by-name caller of each client method of that name. The declared type is then the
+// answer, exactly as for an initializer that is not a creation: an external receiver, a library boundary.
+.decl creation_ref_external(ref:symbol)
+creation_ref_external(ref) :- java_type_reference(kind, "OBJECT_CREATION_TYPE", _, _, _, _, _, "0", tn, _, _, _, _, _, _, _, _, ref),
+ ext_ref_kind(kind),
+ tn != "",
+ ref_file(ref, tn, fp),
+ !name_resolves_in_file(tn, fp),
+ !simple_name_declared(tn),
+ !ref_names_tvar(ref).
+.decl local_creation_external(local:symbol)
+local_creation_external(local) :-
+ java_expression("OBJECT_CREATION", "ROOT", "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, obj),
+ java_type_reference(_, "OBJECT_CREATION_TYPE", _, _, _, _, _, "0", _, _, _, _, _, _, _, obj, "EXPRESSION", ref),
+ creation_ref_external(ref).
expr_type("external", e, x) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e),
expr_ultimate_type("client", e, encl),
@@ -160,9 +195,8 @@ expr_type("external", e, x) :- java_expression(_, _, _, _, _, _, _, _, _, _, nam
// with `Format source()` unstaged), the local is that external type exactly as `Format f` would
// be. Without it the receiver has no external type at all and the call is a declared unknown.
// java_local_variable (19): 11 isVarInferred 13 method 18 hash
-expr_type("external", e, x) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, e),
- expr_ultimate_method("client", e, m),
- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, "true", _, m, _, _, _, _, local),
+expr_type("external", e, x) :- local_use(e, local),
+ java_local_variable(_, _, _, _, _, _, _, _, _, _, _, "true", _, _, _, _, _, _, local),
local_init_expr(local, init),
expr_type("external", init, x).
expr_type("external", e, x) :- java_expression("CAST_EXPRESSION", _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
@@ -213,6 +247,20 @@ expr_call_candidate(call, m) :- call_site(call, name, recv),
external_target(call, label) :- call_site(call, name, recv),
expr_type("external", recv, x),
label = cat(x, cat(".", name)).
+// (a') the receiver is a TYPE NAME no staged IR declares: a static call on the platform or a dependency,
+// `Character.charCount(cp)`, `Objects.equals(a, b)`, `Collections.unmodifiableMap(m)`. A static call never
+// dispatches, so no client method runs behind it. The parser writes the receiver as an identifier it read as a
+// type (not ambiguous with a variable) with the qualified name its imports give; left unresolved, each one was a
+// by-name caller of every client method called `equals`, `valueOf`, `max`, `get`.
+.decl external_static_receiver(recv:symbol, q:symbol)
+external_static_receiver(recv, q) :-
+ java_expression("IDENTIFIER_REFERENCE", "RECEIVER", _, _, _, _, _, _, _, _, n, _, _, _, "TYPE", "", _, q, "false", _, _, _, _, _, recv),
+ q != "", n != "",
+ !qualified_name_declared(q),
+ !simple_name_declared(n).
+external_target(call, label) :- call_site(call, name, recv),
+ external_static_receiver(recv, q),
+ label = cat("external:", cat(q, cat(".", name))).
// The parser writes parameterCount as text, so the written arity is compared as text.
call_arity(call, pc) :- call_argc(call, n), pc = to_string(n).
call_arity(call, "0") :- call_site(call, _, _), !call_has_args(call).
diff --git a/graph/java/engine/resolution/generated-members.dl b/graph/java/engine/resolution/generated-members.dl
index 4d9e9639..355592ea 100644
--- a/graph/java/engine/resolution/generated-members.dl
+++ b/graph/java/engine/resolution/generated-members.dl
@@ -13,12 +13,15 @@
// WHAT IT DOES NOT DO. It declares members, it does not give them bodies. A call
// to a generated accessor is an edge to a member with no statements, which is the
// truth: the accessor reads or writes a field and calls nothing further. Nothing
-// here invents a call edge out of a generated member.
+// here invents a call edge out of a generated member, and that includes a
+// `@Delegate` forwarder: the call reaches the forwarder, not the delegate.
//
-// SCOPE: accessors, and the logger field a class-level annotation declares.
-// `@Builder` needs a synthesized nested type as well as methods, and
-// `@RequiredArgsConstructor` and friends need constructors; neither is here, and the
-// case pins both as still unresolved so the gaps stay visible rather than silent.
+// SCOPE: accessors (with the shapes `@Accessors` and lombok.config give them),
+// `@With`, the logger field a class-level annotation declares and its type,
+// constructors and their `staticName` factories, `@Builder` (a synthesized nested
+// builder type with its setters, `build()`, `builder()` and `toBuilder()`), and
+// `@Delegate` forwarders. `@SuperBuilder`, `@Singular` and a `@Builder` on a
+// constructor or a static method are not modelled.
//
// THE LOGGER FIELD took two goes. Declaring it in `field_declared_in` did nothing,
// because the site was never created to attach it to: field_access.dl:96 calls a
@@ -35,11 +38,13 @@
// convention external-types.dl already uses for a type no IR declares. The bundle
// synthesizes the row from the id, so the member is named in graph.sqlite and
// carries provenance `generated`: a consumer can always tell a member read out of
-// source from one inferred from an annotation.
+// source from one inferred from an annotation. A synthesized builder TYPE is
+// `generated:.`, and its members hang off that name.
//
// STRATIFICATION. `gen_method_live` and everything it is derived from read only
-// PROJECTIONS OVER BASE RELATIONS (annotation_on, field_decl, field_modifier,
-// type_decl), never the expr_type fixpoint. That is what lets it feed
+// PROJECTIONS OVER BASE RELATIONS (annotation_on, ann_arg, field_decl,
+// field_modifier, type_decl, and for `@Delegate` the declared field type and the
+// type hierarchy), never the expr_type fixpoint. That is what lets it feed
// `sig_declared_in`, which is read under the `!shadowed_in_type` negation in
// method-lookup.dl and for that reason may only be built from base facts. The
// `method_declared_in` clause DOES read `lookup_type`, which is seeded from
@@ -47,7 +52,8 @@
// in method-lookup.dl do.
//
// COST. One pass over the annotations already staged, bounded by the number of
-// annotated fields. It adds no join over the library method table.
+// annotated fields. It adds no join over the library method table except for a
+// `@Delegate` field, which reads the methods of that one field's type.
// ============================================================================
// ─────────────────────────────────────────────────────────────────────────────
@@ -61,10 +67,12 @@
// (a) on a FIELD: declares an accessor for that field alone.
gen_field_getter_ann("Getter").
gen_field_setter_ann("Setter").
+gen_field_with_ann("With"). gen_field_with_ann("Wither").
// (b) on a TYPE: declares the accessor for every instance field of the type.
gen_type_getter_ann("Getter"). gen_type_getter_ann("Data").
gen_type_setter_ann("Setter"). gen_type_setter_ann("Data").
+gen_type_with_ann("With"). gen_type_with_ann("Wither").
// `Value` is TWO different annotations sharing a simple name: the processor's, on
// a TYPE, which declares a getter per field; and the framework's config injection,
@@ -74,12 +82,30 @@ gen_type_setter_ann("Setter"). gen_type_setter_ann("Data").
// rather than one list consulted from both places.
gen_type_getter_ann("Value").
-// (c) on a TYPE: declares a logger FIELD of the given name.
+// (c) on a TYPE: declares a logger FIELD of the given name, of the given type.
gen_logger_ann("Slf4j", "log"). gen_logger_ann("XSlf4j", "log").
gen_logger_ann("Log4j", "log"). gen_logger_ann("Log4j2", "log").
gen_logger_ann("CommonsLog", "log"). gen_logger_ann("JBossLog", "log").
gen_logger_ann("Flogger", "log"). gen_logger_ann("Log", "log").
+// The type is fixed by the annotation, so nothing is inferred (#1408).
+gen_logger_type("Slf4j", "org.slf4j.Logger").
+gen_logger_type("XSlf4j", "org.slf4j.ext.XLogger").
+gen_logger_type("Log4j", "org.apache.log4j.Logger").
+gen_logger_type("Log4j2", "org.apache.logging.log4j.Logger").
+gen_logger_type("CommonsLog", "org.apache.commons.logging.Log").
+gen_logger_type("JBossLog", "org.jboss.logging.Logger").
+gen_logger_type("Flogger", "com.google.common.flogger.FluentLogger").
+gen_logger_type("Log", "java.util.logging.Logger").
+
+// (d) the element that turns a constructor annotation into a static factory as well
+// (`@AllArgsConstructor(staticName = "of")`, `@Data(staticConstructor = "of")`).
+gen_static_arg("NoArgsConstructor", "staticName").
+gen_static_arg("AllArgsConstructor", "staticName").
+gen_static_arg("RequiredArgsConstructor", "staticName").
+gen_static_arg("Data", "staticConstructor").
+gen_static_arg("Value", "staticConstructor").
+
// ─────────────────────────────────────────────────────────────────────────────
// 1. WHICH FIELDS CARRY AN ACCESSOR
@@ -91,46 +117,48 @@ gen_logger_ann("Flogger", "log"). gen_logger_ann("Log", "log").
// ─────────────────────────────────────────────────────────────────────────────
gen_field_static(f) :- field_modifier(_, _, mod, f), contains("STATIC", mod).
-// gen_accessor_demand(Prov, OwnerType, FieldName, FieldTypeName, Kind)
-gen_accessor_demand(p, owner, fname, ftype, "get") :-
+// gen_acc(Prov, OwnerType, FieldName, FieldTypeName, FieldHash, Kind): Kind is
+// "get", "set" or "with". Every name, return type and field link below reads this
+// one relation, so the field a member was built from is never lost on the way.
+gen_acc(p, owner, fname, ftype, f, "get") :-
annotation_on(p, ann, _, "FIELD_DECLARATION", f, _), gen_field_getter_ann(ann),
field_decl(p, fname, ftype, owner, f).
-gen_accessor_demand(p, owner, fname, ftype, "set") :-
+gen_acc(p, owner, fname, ftype, f, "set") :-
annotation_on(p, ann, _, "FIELD_DECLARATION", f, _), gen_field_setter_ann(ann),
field_decl(p, fname, ftype, owner, f).
-gen_accessor_demand(p, owner, fname, ftype, "get") :-
+gen_acc(p, owner, fname, ftype, f, "with") :-
+ annotation_on(p, ann, _, "FIELD_DECLARATION", f, _), gen_field_with_ann(ann),
+ field_decl(p, fname, ftype, owner, f).
+gen_acc(p, owner, fname, ftype, f, "get") :-
annotation_on(p, ann, _, "TYPE_DECLARATION", owner, _), gen_type_getter_ann(ann),
field_decl(p, fname, ftype, owner, f), !gen_field_static(f).
-gen_accessor_demand(p, owner, fname, ftype, "set") :-
+gen_acc(p, owner, fname, ftype, f, "set") :-
annotation_on(p, ann, _, "TYPE_DECLARATION", owner, _), gen_type_setter_ann(ann),
field_decl(p, fname, ftype, owner, f), !gen_field_static(f).
+gen_acc(p, owner, fname, ftype, f, "with") :-
+ annotation_on(p, ann, _, "TYPE_DECLARATION", owner, _), gen_type_with_ann(ann),
+ field_decl(p, fname, ftype, owner, f), !gen_field_static(f).
-// gen_accessor_src(Prov, OwnerType, FieldName, FieldHash, Kind) — the same four
-// derivations as above, keeping the FIELD so section 7 can give the getter a return
-// type. Carried separately rather than widening gen_accessor_demand, because that
-// relation is joined on in four places and its shape is the reason the name rules
-// below read cleanly.
-gen_accessor_src(p, owner, fname, f, "get") :-
- annotation_on(p, ann, _, "FIELD_DECLARATION", f, _), gen_field_getter_ann(ann),
- field_decl(p, fname, _, owner, f).
-gen_accessor_src(p, owner, fname, f, "get") :-
- annotation_on(p, ann, _, "TYPE_DECLARATION", owner, _), gen_type_getter_ann(ann),
- field_decl(p, fname, _, owner, f), !gen_field_static(f).
+// gen_accessor_demand(Prov, OwnerType, FieldName, FieldTypeName, Kind): the
+// projection the prefix rules below key on.
+gen_accessor_demand(p, owner, fname, ftype, kind) :- gen_acc(p, owner, fname, ftype, _, kind).
// ─────────────────────────────────────────────────────────────────────────────
-// 1b. THE ACCESSOR PREFIX (lombok.config, #888)
+// 1b. lombok.config (#888, #1406)
//
-// lombok.config decides the NAMES the processor generates, and reading only the field
-// spelling gets them wrong wherever it is set. `lombok.accessors.prefix += m` on a
-// field `mDepth` produces getDepth(), not getMDepth(). The wrong name is worse than no
-// name, for the reason section 2 gives: it DECLARES a member the artefact does not
-// have, and nothing downstream can tell it from a real one.
+// lombok.config decides the NAMES and SHAPES the processor generates, and reading only
+// the field spelling gets them wrong wherever it is set. `lombok.accessors.prefix += m`
+// on a field `mDepth` produces getDepth(), not getMDepth(); `lombok.accessors.fluent =
+// true` produces depth() and depth(v); `lombok.accessors.chain = true` makes every
+// setter return `this`. The wrong name is worse than no name, for the reason section 2
+// gives: it DECLARES a member the artefact does not have, and nothing downstream can
+// tell it from a real one.
//
// The file is in .properties format with no .properties extension, so the parser
// matches it by name (FILE_EXTENSIONS.LOMBOK_CONFIG).
//
// SCOPE. A lombok.config applies to the directory tree below it. Rather than model
-// that tree walk, the prefix is taken per PROVENANCE: one config per parsed root is
+// that tree walk, a key is taken per PROVENANCE: one config per parsed root is
// the shape every module in practice has, and a repository that sets a DIFFERENT
// prefix in two subtrees of one module would need the walk. That case is declared, not
// silently mis-named: gen_prefix_ambiguous drops both and section 2 falls back to the
@@ -142,32 +170,36 @@ gen_accessor_src(p, owner, fname, f, "get") :-
// Getting that wrong would rename every field that happens to start with the prefix
// letter.
// ─────────────────────────────────────────────────────────────────────────────
-gen_prefix_key("lombok.accessors.prefix").
+gen_cfg_key("lombok.accessors.prefix").
+gen_cfg_key("lombok.accessors.chain").
+gen_cfg_key("lombok.accessors.fluent").
-// The value of the prefix key, from a lombok.config on either side.
-gen_prefix_value(prov, v) :- gen_prefix_key(k), prop_key(prov, k, "true", f, h),
+// The raw value of each key, from a lombok.config on either side.
+gen_cfg_value(prov, k, v) :- gen_cfg_key(k), prop_key(prov, k, "true", f, h),
contains("lombok.config", f), prop_segment(prov, h, v, _, _, _, _), v != "".
// `+=` is the idiomatic spelling in this file and the properties scanner does not know
-// it: the line splits on the first space, so the VALUE arrives as "+= m". Peeled here
-// rather than taught to the scanner, because `+=` is a lombok.config-ism and a
-// .properties file that happens to contain one must keep splitting the way it does
-// today. Repeated single-character peeling, the same shape cfg_strip uses for quotes.
-// A prefix is an identifier fragment, so it can never legitimately start with one of
-// these three characters and nothing real is eaten.
+// it: the line splits on the first space, so the VALUE arrives as "+= m" (and "= true"
+// for a key written `key = true`). Peeled here rather than taught to the scanner,
+// because `+=` is a lombok.config-ism and a .properties file that happens to contain one
+// must keep splitting the way it does today. Repeated single-character peeling, the same
+// shape cfg_strip uses for quotes. A value here is an identifier fragment or a boolean,
+// so it can never legitimately start with one of these three characters and nothing
+// real is eaten.
gen_pfx_junk(s) :- gen_pfx_step(_, s), strlen(s) > 0,
substr(s, 0, 1) = "+".
gen_pfx_junk(s) :- gen_pfx_step(_, s), strlen(s) > 0,
substr(s, 0, 1) = "=".
gen_pfx_junk(s) :- gen_pfx_step(_, s), strlen(s) > 0,
substr(s, 0, 1) = " ".
-gen_pfx_step(v, v) :- gen_prefix_value(_, v).
+gen_pfx_step(v, v) :- gen_cfg_value(_, _, v).
gen_pfx_step(v, r) :- gen_pfx_step(v, s), gen_pfx_junk(s), strlen(s) > 1,
r = substr(s, 1, strlen(s) - 1).
-gen_prefix_raw(prov, c) :- gen_prefix_value(prov, v), gen_pfx_step(v, c),
+gen_cfg_clean(prov, k, c) :- gen_cfg_value(prov, k, v), gen_pfx_step(v, c),
!gen_pfx_junk(c), c != "".
+gen_prefix_raw(prov, c) :- gen_cfg_clean(prov, "lombok.accessors.prefix", c).
gen_prefix(prov, v) :- gen_prefix_raw(prov, v), !gen_prefix_ambiguous(prov).
gen_prefix_ambiguous(prov) :- gen_prefix_raw(prov, a), gen_prefix_raw(prov, b), a != b.
@@ -187,6 +219,36 @@ gen_effective_name(prov, fname, stripped) :- gen_prefix_applies(prov, fname, str
gen_effective_name(prov, fname, fname) :- gen_accessor_demand(prov, _, fname, _, _),
!gen_prefix_applies(prov, fname, _).
+// ─────────────────────────────────────────────────────────────────────────────
+// 1c. THE ACCESSOR SHAPE: @Accessors(fluent = true | chain = true) (#1406)
+//
+// `fluent = true` names the accessors after the field (`label()`, `label(v)`) instead of
+// get/set, and implies `chain = true` unless chain is written false. `chain = true` makes
+// a setter return the owner, so `s.setHost(h).setPort(1)` is one chain. The annotation on
+// the FIELD decides; else the one on its TYPE; else the lombok.config key. Each option is
+// decided on its own, which is how the processor merges them.
+// ─────────────────────────────────────────────────────────────────────────────
+gen_style_opt("chain").
+gen_style_opt("fluent").
+
+gen_acc_ann_field(f, opt, v) :- annotation_on(_, "Accessors", _, "FIELD_DECLARATION", f, a),
+ ann_arg(_, a, opt, v, _, _), gen_style_opt(opt).
+gen_acc_ann_type(owner, opt, v) :- annotation_on(_, "Accessors", _, "TYPE_DECLARATION", owner, a),
+ ann_arg(_, a, opt, v, _, _), gen_style_opt(opt).
+gen_acc_field_has(f, opt) :- gen_acc_ann_field(f, opt, _).
+gen_acc_type_has(owner, opt) :- gen_acc_ann_type(owner, opt, _).
+
+gen_style(f, opt, v) :- gen_acc(_, _, _, _, f, _), gen_acc_ann_field(f, opt, v).
+gen_style(f, opt, v) :- gen_acc(_, owner, _, _, f, _), gen_acc_ann_type(owner, opt, v),
+ !gen_acc_field_has(f, opt).
+gen_style(f, opt, v) :- gen_acc(p, owner, _, _, f, _), gen_style_opt(opt),
+ !gen_acc_field_has(f, opt), !gen_acc_type_has(owner, opt),
+ gen_cfg_clean(p, k, v), k = cat("lombok.accessors.", opt).
+
+gen_fluent(f) :- gen_style(f, "fluent", "true").
+gen_chain(f) :- gen_style(f, "chain", "true").
+gen_chain(f) :- gen_fluent(f), !gen_style(f, "chain", "false").
+
// ─────────────────────────────────────────────────────────────────────────────
// 2. THE NAME
//
@@ -196,42 +258,72 @@ gen_effective_name(prov, fname, fname) :- gen_accessor_demand(prov, _, fname, _,
// declares a method the artefact does not have, and an invented member is worse
// than a missing one, because nothing downstream can tell it from a real edge.
//
+// A primitive boolean ALREADY named `is` + an uppercase letter keeps its name for the
+// getter and drops the `is` for the setter and the wither: `isLocked` -> `isLocked()`,
+// `setLocked(v)`, `withLocked(v)` (#1404). `island` is not such a name, because `l` is
+// lowercase: it is `isIsland()`, the ordinary rule.
+//
// cfg_upper is reused rather than re-tabled. Souffle sees one program: every .decl
// is central in souffle/decls_all.dl and a rule may read a relation defined in any
// phase folder, so the 26 facts stay in the one place that already holds them.
// ─────────────────────────────────────────────────────────────────────────────
-// Capitalize the EFFECTIVE name, which is the field name with any configured accessor
-// prefix removed. gen_effective_name is the identity when no prefix is configured, so
-// a project without a lombok.config derives exactly what it derived before.
-gen_capitalized(n, c) :- gen_effective_name(_, _, n), strlen(n) > 0,
+gen_is_stripped(p, f, s) :- gen_acc(p, _, fname, "boolean", f, _),
+ gen_effective_name(p, fname, eff), strlen(eff) > 2,
+ substr(eff, 0, 2) = "is", cfg_lower(substr(eff, 2, 1), _),
+ s = substr(eff, 2, strlen(eff) - 2).
+
+// gen_prop(Prov, FieldHash, PropertyName): the name the get/set/with prefix goes on.
+gen_prop(p, f, s) :- gen_is_stripped(p, f, s).
+gen_prop(p, f, eff) :- gen_acc(p, _, fname, _, f, _),
+ gen_effective_name(p, fname, eff), !gen_is_stripped(p, f, _).
+
+// Capitalize the property name, which is the field name with any configured accessor
+// prefix (and a boolean's `is`) removed.
+gen_cap_demand(n) :- gen_prop(_, _, n).
+gen_capitalized(n, c) :- gen_cap_demand(n), strlen(n) > 0,
cfg_upper(substr(n, 0, 1), up), c = cat(up, substr(n, 1, strlen(n) - 1)).
-// ALREADY capitalized, which is what stripping an accessor prefix leaves behind:
-// `mDepth` strips to `Depth`. cfg_upper maps lowercase to uppercase and has no key for
-// an uppercase letter, so without this clause the stripped name capitalizes to nothing
-// and the accessor is not declared at all, under either name.
-gen_capitalized(n, n) :- gen_effective_name(_, _, n), strlen(n) > 0,
+// ALREADY capitalized, which is what stripping a prefix leaves behind: `mDepth` strips
+// to `Depth`. cfg_upper maps lowercase to uppercase and has no key for an uppercase
+// letter, so without this clause the stripped name capitalizes to nothing and the
+// accessor is not declared at all, under either name.
+gen_capitalized(n, n) :- gen_cap_demand(n), strlen(n) > 0,
cfg_lower(substr(n, 0, 1), _).
-// gen_accessor(Prov, OwnerType, MethodName, Arity)
+// gen_acc_named(Prov, OwnerType, FieldHash, Kind, MethodName, Arity)
// Arity is a SYMBOL, not a number, because sig_declared_in carries parameterCount
// as the symbol the IR wrote; a number here would not join with it.
-gen_accessor(p, owner, cat("get", c), "0") :-
- gen_accessor_demand(p, owner, fname, ftype, "get"), ftype != "boolean",
- gen_effective_name(p, fname, eff), gen_capitalized(eff, c).
-gen_accessor(p, owner, cat("is", c), "0") :-
- gen_accessor_demand(p, owner, fname, "boolean", "get"),
- gen_effective_name(p, fname, eff), gen_capitalized(eff, c).
-gen_accessor(p, owner, cat("set", c), "1") :-
- gen_accessor_demand(p, owner, fname, _, "set"),
- gen_effective_name(p, fname, eff), gen_capitalized(eff, c).
+gen_acc_named(p, owner, f, "get", cat("get", c), "0") :- gen_acc(p, owner, _, ftype, f, "get"),
+ !gen_fluent(f), ftype != "boolean", gen_prop(p, f, n), gen_capitalized(n, c).
+gen_acc_named(p, owner, f, "get", cat("is", c), "0") :- gen_acc(p, owner, _, "boolean", f, "get"),
+ !gen_fluent(f), gen_prop(p, f, n), gen_capitalized(n, c).
+gen_acc_named(p, owner, f, "set", cat("set", c), "1") :- gen_acc(p, owner, _, _, f, "set"),
+ !gen_fluent(f), gen_prop(p, f, n), gen_capitalized(n, c).
+// fluent: the name is the field's, after the prefix is removed (a boolean keeps its `is`).
+gen_acc_named(p, owner, f, "get", eff, "0") :- gen_acc(p, owner, fname, _, f, "get"),
+ gen_fluent(f), gen_effective_name(p, fname, eff).
+gen_acc_named(p, owner, f, "set", eff, "1") :- gen_acc(p, owner, fname, _, f, "set"),
+ gen_fluent(f), gen_effective_name(p, fname, eff).
+// @With is not affected by fluent: it is always `with` + the property name.
+gen_acc_named(p, owner, f, "with", cat("with", c), "1") :- gen_acc(p, owner, _, _, f, "with"),
+ gen_prop(p, f, n), gen_capitalized(n, c).
+
+// gen_accessor(Prov, OwnerType, MethodName, Arity)
+gen_accessor(p, owner, name, pc) :- gen_acc_named(p, owner, _, _, name, pc).
// ─────────────────────────────────────────────────────────────────────────────
// 3. THE MEMBERS, AND THEIR IDS
+//
+// gen_member(Prov, OwnerType, OwnerQualifiedName, Name, Arity) is the ONE list every
+// kind of generated method joins: the accessors here, and the factories, builder members
+// and forwarders of sections 6-9. The owner's qualified name is carried because a
+// synthesized builder type is in no type_decl row to read it from.
// ─────────────────────────────────────────────────────────────────────────────
+gen_member(p, owner, q, name, pc) :- gen_accessor(p, owner, name, pc),
+ type_decl(p, _, q, _, _, _, owner).
+
// gen_method(OwnerType, Name, Arity, MethodId)
-gen_method(owner, name, pc, id) :- gen_accessor(p, owner, name, pc),
- type_decl(p, _, qname, _, _, _, owner),
- id = cat("generated:", cat(qname, cat("#", cat(name, cat("/", pc))))).
+gen_method(owner, name, pc, id) :- gen_member(_, owner, q, name, pc),
+ id = cat("generated:", cat(q, cat("#", cat(name, cat("/", pc))))).
// ─────────────────────────────────────────────────────────────────────────────
@@ -271,18 +363,18 @@ sig_concrete_in(t, name, pc) :- gen_method_live(t, name, pc, _).
// method_owner, and projections/methods.dl builds that from java_method /
// lib_method alone. Without this clause a generated member reached expr_pruned and
// then produced nothing: it owns no row there. The provenance carried through from
-// gen_accessor is what decides `known_edge` against `boundary_lib`, so a generated
+// gen_member is what decides `known_edge` against `boundary_lib`, so a generated
// member on a library type is a boundary edge exactly as a hand-written one is.
method_owner(p, owner, id) :- gen_method_live(owner, name, pc, id),
- gen_accessor(p, owner, name, pc).
+ gen_member(p, owner, _, name, pc).
// ARITY. overload.dl counts parameters from java_method_parameter /
// lib_method_parameter, so a generated method has no count and only the zero-arg
// clause of arity_match can fire: every getter resolved and every SETTER was
-// dropped at the arity gate. A setter takes exactly one argument, which the
-// annotation determines without any parameter row, so state it directly. The
-// getters need no clause: zero args against zero params is already a match.
-method_paramc(m, 1) :- gen_method_live(_, _, "1", m).
+// dropped at the arity gate. The annotation determines the count without any
+// parameter row, so state it directly. Zero args against zero params is already a
+// match, which is why the zero-arity members need no row.
+method_paramc(m, n) :- gen_method_live(_, _, pc, m), pc != "0", n = to_number(pc).
// gen_field(OwnerType, Name, FieldId)
gen_field(owner, fname, id) :-
@@ -313,6 +405,34 @@ recv_name_is_field(e) :- ident_field_recv(e, name), expr_ultimate_type("client",
field_prov(p, id) :- gen_field_live(owner, fname, id),
annotation_on(p, ann, _, "TYPE_DECLARATION", owner, _), gen_logger_ann(ann, fname).
+// ── 4b. THE LOGGER FIELD'S TYPE (#1408) ────────────────────────────────────
+// The field was declared by name only, so `log.info(..)` had a receiver of no type and
+// the call was unresolved, while the same call through a hand-declared Logger field is
+// a library boundary. The annotation fixes the type: a staged library type when there
+// is one, a client type of that name, and otherwise the external node external-types.dl
+// mints for any type the client names and no IR declares.
+gen_field_type_q(id, q) :- gen_field_live(owner, fname, id),
+ annotation_on(_, ann, _, "TYPE_DECLARATION", owner, _), gen_logger_ann(ann, fname),
+ gen_logger_type(ann, q).
+gen_field_type(id, "lib", t) :- gen_field_type_q(id, q),
+ lib_type(_, q, _, _, _, _, _, _, _, _, _, _, _, t).
+gen_field_type(id, "client", t) :- gen_field_type_q(id, q),
+ java_type(_, q, _, _, _, _, _, _, _, _, _, _, _, t).
+gen_field_type(id, "external", t) :- gen_field_type_q(id, q),
+ !qualified_name_declared(q), t = cat("external:", q).
+external_type(t) :- gen_field_type(_, "external", t).
+
+// The receiver: a bare name the parser tagged FIELD, in the declaring type, a subtype of
+// it, or a type nested in it (a logger used from an inner or anonymous class).
+gen_field_recv(e, id) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e),
+ expr_ultimate_type("client", e, encl), gen_field_live(encl, name, id).
+gen_field_recv(e, id) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e),
+ expr_ultimate_type("client", e, encl), type_ancestor(encl, anc), gen_field_live(anc, name, id).
+gen_field_recv(e, id) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e),
+ expr_ultimate_type("client", e, encl), type_in_type("client", encl, outer),
+ gen_field_live(outer, name, id).
+expr_type(prov, e, t) :- gen_field_recv(e, id), gen_field_type(id, prov, t).
+
// ─────────────────────────────────────────────────────────────────────────────
// 5. OVERRIDE, which is a sixth relation built off the IR tables
//
@@ -462,6 +582,22 @@ ctor_of("client", owner, id) :- gen_ctor_live(owner, _, id).
method_paramc(id, n) :- gen_ctor_live(_, n, id), n > 0.
method_owner("client", owner, id) :- gen_ctor_live(owner, _, id).
+// ── 6b. THE STATIC FACTORY (#1407) ─────────────────────────────────────────
+// `@AllArgsConstructor(staticName = "of")` makes the constructor private and declares
+// `static Point of(int x, int y)` beside it, with the constructor's arity. `Point.of(1,
+// 2)` names no constructor, so without this the call and everything chained onto it
+// were unresolved.
+gen_ctor_static(p, owner, shape, v) :- annotation_on(p, ann, _, "TYPE_DECLARATION", owner, a),
+ gen_ctor_ann(ann, shape), gen_static_arg(ann, arg), ann_arg(_, a, arg, v, _, _), v != "".
+gen_ctor_arity(owner, "none", 0) :- gen_ctor_demand(_, owner, "none").
+gen_ctor_arity(owner, "all", n) :- gen_ctor_all_count(owner, n).
+gen_ctor_arity(owner, "required", n) :- gen_ctor_req_count(owner, n).
+
+gen_member(p, owner, q, v, to_string(n)) :- gen_ctor_static(p, owner, shape, v),
+ gen_ctor_arity(owner, shape, n), type_decl(p, _, q, _, _, _, owner).
+gen_ret_self(id, owner) :- gen_ctor_static(_, owner, shape, v),
+ gen_ctor_arity(owner, shape, n), gen_method_live(owner, v, to_string(n), id).
+
// ─────────────────────────────────────────────────────────────────────────────
// 7. THE RETURN TYPE (#885)
@@ -477,9 +613,10 @@ method_owner("client", owner, id) :- gen_ctor_live(owner, _, id).
// next door as field_type_resolves, so this is a join, not new machinery: the getter
// returns exactly what the field holds.
//
-// Getters only. A setter returns void, and @Accessors(chain = true) making it return
-// `this` is a different annotation with a different answer, deliberately not guessed
-// here.
+// A member that returns its OWNER (a chain setter, a wither, a static factory, every
+// builder member) has no reference to reuse either, so it gets a synthetic one,
+// `generated-ref:`. It names no IR node, so every consumer that walks a reference's
+// type arguments finds none, which is the truth: the owner is written without any.
//
// gen_accessor_field(MethodId, FieldHash) — which field a generated getter reads.
//
@@ -491,25 +628,138 @@ method_owner("client", owner, id) :- gen_ctor_live(owner, _, id).
//
// Getters only. A setter WRITES the field, and cfg_field_read is a read relation;
// modelling the write is a separate question with a separate consumer.
-gen_accessor_field(id, f) :- gen_accessor_src(_, owner, fname, f, "get"),
- gen_capitalized(fname, c),
- gen_method_live(owner, cat("get", c), "0", id).
-gen_accessor_field(id, f) :- gen_accessor_src(_, owner, fname, f, "get"),
- gen_capitalized(fname, c),
- gen_method_live(owner, cat("is", c), "0", id).
-
-// The REF column is the field's own type reference. Reusing it rather than minting a
-// synthetic one keeps every downstream consumer that reads the reference (type
-// arguments, generic binding) pointing at a node the IR actually has.
// ─────────────────────────────────────────────────────────────────────────────
-gen_return_type(id, ref, t) :- gen_accessor_src(p, owner, fname, f, "get"),
- gen_capitalized(fname, c),
- gen_method_live(owner, cat("get", c), "0", id),
- field_type_resolves(p, f, ref, t).
-gen_return_type(id, ref, t) :- gen_accessor_src(p, owner, fname, f, "get"),
- gen_capitalized(fname, c),
- gen_method_live(owner, cat("is", c), "0", id),
- field_type_resolves(p, f, ref, t).
-
-method_return_type_resolves(p, id, ref, t) :- gen_return_type(id, ref, t),
- gen_method_live(owner, _, _, id), type_decl(p, _, _, _, _, _, owner).
+gen_accessor_field(id, f) :- gen_acc_named(_, owner, f, "get", name, "0"),
+ gen_method_live(owner, name, "0", id).
+
+// gen_return_type(MethodId, Ref, Type): the getter returns the field's own type,
+// through the field's own reference.
+gen_return_type(id, ref, t) :- gen_accessor_field(id, f),
+ field_type_resolves(_, f, ref, t).
+
+// A setter returns the owner when it chains (#1406), and a wither always does (#1407).
+gen_ret_self(id, owner) :- gen_acc_named(_, owner, f, "set", name, "1"), gen_chain(f),
+ gen_method_live(owner, name, "1", id).
+gen_ret_self(id, owner) :- gen_acc_named(_, owner, _, "with", name, "1"),
+ gen_method_live(owner, name, "1", id).
+gen_return_type(id, ref, t) :- gen_ret_self(id, t), ref = cat("generated-ref:", id).
+
+// The provenance is the RESOLVED TYPE's, as for a record accessor (reference-types.dl):
+// a client type returning a library type is the ordinary case.
+method_return_type_resolves("client", id, ref, t) :- gen_return_type(id, ref, t),
+ type_prov(t, "client").
+method_return_type_resolves("lib", id, ref, t) :- gen_return_type(id, ref, t),
+ type_prov(t, "lib").
+method_return_type_resolves("client", id, ref, t) :- gen_return_type(id, ref, t),
+ gen_builder_type(_, _, t, _).
+
+
+// ─────────────────────────────────────────────────────────────────────────────
+// 8. @Builder (#1405)
+//
+// `@Builder` declares a nested `Builder` with one setter per field (named after
+// the field, returning the builder), `build()` returning the owner, a static `builder()`
+// on the owner, and, with `toBuilder = true`, an instance `toBuilder()`. None of it is in
+// the source, so every call in `Order.builder().sku("a").build().ship()` was unresolved,
+// including the hand-written `ship()` at its end.
+//
+// THE BUILDER TYPE. When the source writes a partial builder (`public static class
+// OrderBuilder { ... }` inside Order, which the processor then fills), that type is the
+// builder and its hand-written members win as they do anywhere else. Otherwise the type
+// is synthesized: `generated:.`, a lookup type with no file,
+// whose only members are the ones this section declares.
+//
+// builderClassName, builderMethodName and buildMethodName are read; toBuilder must be
+// written `true`. A final field with an initialiser has no setter, as in the processor.
+// ─────────────────────────────────────────────────────────────────────────────
+gen_builder(p, owner, a) :- annotation_on(p, "Builder", _, "TYPE_DECLARATION", owner, a).
+
+gen_builder_opt(owner, opt, v) :- gen_builder(_, owner, a), ann_arg(_, a, opt, v, _, _), v != "".
+gen_builder_opt_has(owner, opt) :- gen_builder_opt(owner, opt, _).
+
+// gen_builder_name(Owner, Which, Name)
+gen_builder_name(owner, "class", v) :- gen_builder_opt(owner, "builderClassName", v).
+gen_builder_name(owner, "class", cat(tn, "Builder")) :- gen_builder(p, owner, _),
+ !gen_builder_opt_has(owner, "builderClassName"), type_decl(p, tn, _, _, _, _, owner).
+gen_builder_name(owner, "method", v) :- gen_builder_opt(owner, "builderMethodName", v).
+gen_builder_name(owner, "method", "builder") :- gen_builder(_, owner, _),
+ !gen_builder_opt_has(owner, "builderMethodName").
+gen_builder_name(owner, "build", v) :- gen_builder_opt(owner, "buildMethodName", v).
+gen_builder_name(owner, "build", "build") :- gen_builder(_, owner, _),
+ !gen_builder_opt_has(owner, "buildMethodName").
+
+// gen_builder_type(Prov, Owner, BuilderType, BuilderQualifiedName)
+gen_builder_written(owner, bt, bq) :- gen_builder_name(owner, "class", bn),
+ type_direct_parent("client", bt, owner),
+ java_type(bn, bq, _, _, _, _, _, _, _, _, _, _, _, bt).
+gen_builder_has_written(owner) :- gen_builder_written(owner, _, _).
+gen_builder_type(p, owner, bt, bq) :- gen_builder(p, owner, _), gen_builder_written(owner, bt, bq).
+gen_builder_type(p, owner, bt, bq) :- gen_builder(p, owner, _), !gen_builder_has_written(owner),
+ gen_builder_name(owner, "class", bn), type_decl(p, _, q, _, _, _, owner),
+ bq = cat(q, cat(".", bn)), bt = cat("generated:", bq).
+
+lookup_type(bt) :- gen_builder_type(_, _, bt, _).
+
+// The fields with a setter: every instance field but a final one already initialised.
+gen_builder_field_skip(f) :- field_modifier(_, _, mod, f), contains("FINAL", mod),
+ gen_field_initialized(f).
+gen_builder_field(p, owner, fname) :- gen_builder(p, owner, _),
+ field_decl(p, fname, _, owner, f), !gen_field_static(f), !gen_builder_field_skip(f).
+
+// On the owner: builder() and toBuilder(), both returning the builder.
+gen_member(p, owner, q, bm, "0") :- gen_builder_type(p, owner, _, _),
+ gen_builder_name(owner, "method", bm), type_decl(p, _, q, _, _, _, owner).
+gen_member(p, owner, q, "toBuilder", "0") :- gen_builder_type(p, owner, _, _),
+ gen_builder_opt(owner, "toBuilder", "true"), type_decl(p, _, q, _, _, _, owner).
+gen_ret_self(id, bt) :- gen_builder_type(_, owner, bt, _), gen_builder_name(owner, "method", bm),
+ gen_method_live(owner, bm, "0", id).
+gen_ret_self(id, bt) :- gen_builder_type(_, owner, bt, _), gen_builder_opt(owner, "toBuilder", "true"),
+ gen_method_live(owner, "toBuilder", "0", id).
+
+// On the builder: a setter per field returning the builder, and build() returning the owner.
+gen_member(p, bt, bq, fname, "1") :- gen_builder_type(p, owner, bt, bq),
+ gen_builder_field(p, owner, fname).
+gen_member(p, bt, bq, b, "0") :- gen_builder_type(p, owner, bt, bq),
+ gen_builder_name(owner, "build", b).
+gen_ret_self(id, bt) :- gen_builder_type(_, owner, bt, _), gen_builder_field(_, owner, fname),
+ gen_method_live(bt, fname, "1", id).
+gen_ret_self(id, owner) :- gen_builder_type(_, owner, bt, _), gen_builder_name(owner, "build", b),
+ gen_method_live(bt, b, "0", id).
+
+
+// ─────────────────────────────────────────────────────────────────────────────
+// 9. @Delegate (#1407)
+//
+// `@Delegate private final Store inner` declares, on the owner, one forwarding method
+// for each public instance method of the field's type. `new Cache().put("a")` names a
+// method Cache does not write, so it was unresolved. The forwarder takes the delegated
+// method's arity and return type. `types =` and `excludes =` are not read.
+//
+// BASE FACTS ONLY, and that decides the scope. A forwarder feeds sig_declared_in like any
+// generated member, so it may not read the type-resolution fixpoint: the field's declared
+// type is resolved by name in the owner's file (type_resolves_in_file, which reads imports
+// and declarations), not through field_type_resolves, and only the methods the type
+// DECLARES are forwarded, not the ones it inherits (type_ancestor is in that fixpoint too).
+// A first draft read both and the program did not stratify.
+// ─────────────────────────────────────────────────────────────────────────────
+gen_delegate(p, owner, ft) :- annotation_on(p, "Delegate", _, "FIELD_DECLARATION", f, _),
+ field_decl(p, _, tn, owner, f), type_decl(p, _, _, fp, _, _, owner),
+ type_resolves_in_file(_, tn, fp, ft).
+
+gen_delegate_kind("INSTANCE_METHOD").
+gen_delegate_kind("ABSTRACT_METHOD").
+
+// gen_delegate_target(FieldType, Name, Arity, DelegatedMethod)
+gen_delegate_target(ft, name, pc, m) :- gen_delegate(_, _, ft), gen_delegate_kind(k),
+ java_method(name, _, _, _, _, _, _, ft, _, _, _, _, _, _, _, _, k, pc, _, _, _, m), name != "".
+gen_delegate_target(ft, name, pc, m) :- gen_delegate(_, _, ft), gen_delegate_kind(k),
+ lib_method(name, _, _, _, _, _, _, ft, _, _, _, _, _, _, _, _, k, pc, _, _, _, m), name != "".
+
+gen_member(p, owner, q, name, pc) :- gen_delegate(p, owner, ft),
+ gen_delegate_target(ft, name, pc, _), type_decl(p, _, q, _, _, _, owner).
+
+gen_return_type(id, ref, t) :- gen_delegate(_, owner, ft), gen_delegate_target(ft, name, pc, m),
+ gen_method_live(owner, name, pc, id), method_return_type_resolves(_, m, ref, t).
+gen_return_type(id, ref, t) :- gen_delegate(_, owner, ft), gen_delegate_target(ft, name, pc, m),
+ gen_method_live(owner, name, pc, id), lib_method_return_type(m, t),
+ ref = cat("generated-ref:", id).
diff --git a/graph/java/engine/resolution/generic-chain.dl b/graph/java/engine/resolution/generic-chain.dl
index 4c2ccdf3..a9e35fae 100644
--- a/graph/java/engine/resolution/generic-chain.dl
+++ b/graph/java/engine/resolution/generic-chain.dl
@@ -100,10 +100,7 @@ expr_arg_binding(call, nested, nestedParam, arg) :- call_site(call, _, recv),
// the method it actually appears in — same guard the recv_decl_ref clauses in expr-type.dl use.
// java_expression (25): 11 name 15 referenceKind 25 hash. java_local_variable (19): 1 name 14 method 19 hash.
expr_arg_binding(recv, bt, param, arg) :-
- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, recv),
- expr_ultimate_method("client", recv, m),
- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local),
- java_expression("METHOD_INVOCATION", _, "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, initExpr),
+ local_use_init(recv, initExpr),
expr_arg_binding(initExpr, bt, param, arg).
// ── INHERITED generics with a CONCRETE super-arg (c9): `class DogBox extends Box`.
@@ -129,10 +126,134 @@ type_fixes_super(sub, super, superParam, argType) :-
java_type_reference(k, _, _, _, _, sref, pos, "1", _, _, _, _, _, _, _, _, _, argRef),
k != "TYPE_VARIABLE",
type_ref_resolves(_, argRef, argType).
+// class_tvar_binding(Type, Super, SuperParam, Arg): as seen from Type, Super's parameter is
+// Arg. Seeded by the clause that fixes it, then carried two ways (#1478, #1412):
+// UP, through a supertype that passes its own variable straight on. `ChainStub extends
+// Mid`, `Mid extends Base`: Mid.S = ChainStub, so Base.S = ChainStub.
+// Binding only the direct parent lost `withTimeout`'s S, declared on Base, and the call
+// after it; one level (`DirectStub extends Base`) resolved.
+// DOWN, to every subtype of the type that fixed it: a subclass of OrderService inherits
+// Base.M = OrderMapper exactly as OrderService does.
+// Bounded by the hierarchy (a finite DAG), and seeded only where a clause writes a concrete
+// argument, so a type that passes a variable through adds nothing by itself.
+class_tvar_binding(sub, super, superParam, argType) :- type_fixes_super(sub, super, superParam, argType).
+class_tvar_binding(sub, super2, p2, argType) :- class_tvar_binding(sub, super1, p1, argType),
+ super_tvar_rename(super1, super2, p2, p1).
+class_tvar_binding(sub2, super, p, argType) :- class_tvar_binding(sub, super, p, argType),
+ type_parent(sub2, sub), sub2 != sub.
+
// a receiver whose type fixes a supertype parameter carries that binding.
expr_arg_binding(recv, super, superParam, argType) :-
expr_type(_, recv, subType),
- type_fixes_super(subType, super, superParam, argType).
+ class_tvar_binding(subType, super, superParam, argType).
+
+// ── THE RECEIVER IS IMPLICIT: an inherited member typed as the superclass's variable (#1412) ──
+// `class OrderService extends Base` calling `getMapper().findClosed()` or
+// `mapper.findOpen()`: the member is Base's, typed M, and the receiver is `this`, which no
+// clause above sees because nothing is written at the call. The binding is the enclosing
+// type's own, from its extends clause. The field form was worse than unresolved: M, read as
+// a class name nothing declares, became an external type and the call a library boundary on
+// `external:M` (external-types.dl now refuses a type variable's name).
+expr_type(prov, call, argType) :- call_unqualified(call, _),
+ applicable_candidate(call, callee),
+ java_method_ret_tvar(callee, owner, tv),
+ expr_ultimate_type("client", call, encl),
+ class_tvar_binding(encl, owner, tv, argType),
+ type_prov(argType, prov).
+expr_type(prov, call, argType) :- call_unqualified(call, _),
+ applicable_candidate(call, callee),
+ lib_method_ret_tvar(callee, owner, tv),
+ expr_ultimate_type("client", call, encl),
+ class_tvar_binding(encl, owner, tv, argType),
+ type_prov(argType, prov).
+
+// field_tvar(Field, OwnerType, TypeVariable): a field declared as its owner's type
+// variable. The parser writes a field's variable as a CLASS reference carrying the name
+// (the TYPE_VARIABLE kind appears in method signatures), so both encodings are read.
+// java_type_reference (18): 0 kind 1 context 2 typeRegistryLinkHash 7 depth 8 typeName
+// 10 typeVariableName 15 ownerHash 16 ownerKind
+field_tvar(field, owner, tv) :- java_field(_, _, _, _, _, _, _, _, owner, _, _, _, _, field),
+ java_type_reference("CLASS", "FIELD_TYPE", _, _, _, _, _, "0", tv, _, _, _, _, _, _, field, "FIELD", _),
+ java_type_parameter(tv, _, _, _, _, _, owner, _).
+field_tvar(field, owner, tv) :- java_field(_, _, _, _, _, _, _, _, owner, _, _, _, _, field),
+ java_type_reference("TYPE_VARIABLE", "FIELD_TYPE", _, _, _, _, _, "0", _, _, tv, _, _, _, _, field, "FIELD", _),
+ java_type_parameter(tv, _, _, _, _, _, owner, _).
+field_tvar(field, owner, tv) :- lib_field(_, _, _, _, _, _, _, _, owner, _, _, _, _, field),
+ lib_type_reference("CLASS", "FIELD_TYPE", _, _, _, _, _, "0", tv, _, _, _, _, _, _, field, "FIELD", _),
+ lib_type_parameter(tv, _, _, _, _, _, owner, _).
+field_tvar(field, owner, tv) :- lib_field(_, _, _, _, _, _, _, _, owner, _, _, _, _, field),
+ lib_type_reference("TYPE_VARIABLE", "FIELD_TYPE", _, _, _, _, _, "0", _, _, tv, _, _, _, _, field, "FIELD", _),
+ lib_type_parameter(tv, _, _, _, _, _, owner, _).
+
+// a bare inherited field: the enclosing type's binding for its declaring type's variable.
+expr_type(prov, e, argType) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e),
+ expr_ultimate_type("client", e, encl),
+ type_ancestor(encl, anc),
+ java_field(name, _, _, _, _, _, _, _, anc, _, _, _, _, field),
+ field_tvar(field, anc, tv),
+ class_tvar_binding(encl, anc, tv, argType),
+ type_prov(argType, prov).
+expr_type(prov, e, argType) :- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "FIELD", _, _, _, _, _, _, _, _, _, e),
+ expr_ultimate_type("client", e, encl),
+ type_ancestor(encl, anc),
+ lib_field(name, _, _, _, _, _, _, _, anc, _, _, _, _, field),
+ field_tvar(field, anc, tv),
+ class_tvar_binding(encl, anc, tv, argType),
+ type_prov(argType, prov).
+// `this.mapper` / `svc.mapper`: the same, keyed on the qualifier's type.
+expr_type(prov, e, argType) :- java_expression("FIELD_ACCESS", _, _, _, _, _, _, _, _, _, name, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
+ java_expression(_, "QUALIFIER", _, _, _, _, e, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, q),
+ expr_type(_, q, qtype),
+ type_ancestor(qtype, anc),
+ java_field(name, _, _, _, _, _, _, _, anc, _, _, _, _, field),
+ field_tvar(field, anc, tv),
+ class_tvar_binding(qtype, anc, tv, argType),
+ type_prov(argType, prov).
+expr_type(prov, e, argType) :- java_expression("FIELD_ACCESS", _, _, _, _, _, _, _, _, _, name, _, _, _, _, _, _, _, _, _, _, _, _, _, e),
+ java_expression(_, "QUALIFIER", _, _, _, _, e, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, q),
+ expr_type(_, q, qtype),
+ type_ancestor(qtype, anc),
+ lib_field(name, _, _, _, _, _, _, _, anc, _, _, _, _, field),
+ field_tvar(field, anc, tv),
+ class_tvar_binding(qtype, anc, tv, argType),
+ type_prov(argType, prov).
+
+// ── A GENERIC METHOD'S OWN VARIABLE, BOUND BY A CLASS LITERAL (#1547) ───────────────────────
+// `static T get(Class type)`, called `Beans.get(OrderStore.class).save("a")`: T is the
+// METHOD's type parameter, and the only place it is fixed is the argument. Every clause above
+// binds a CLASS's parameters from a receiver, so the call had no type and `save` was
+// unresolved: the service-locator lookup (`getBean(X.class)`, `getMapper(X.class)`). The
+// binding is sound where it applies: the argument IS `Class`, so T is X.
+// Only a class literal written at the call binds; a Class-typed variable does not.
+mtvar_ret(m, tv) :- java_type_reference("TYPE_VARIABLE", "METHOD_RETURN", _, _, _, _, _, "0", _, _, tv, _, _, _, _, m, "METHOD", _),
+ tv != "", java_method_type_parameter(tv, _, _, _, _, _, _, m, _, _).
+mtvar_ret(m, tv) :- lib_type_reference("TYPE_VARIABLE", "METHOD_RETURN", _, _, _, _, _, "0", _, _, tv, _, _, _, _, m, "METHOD", _),
+ tv != "", lib_method_type_parameter(tv, _, _, _, _, _, _, m, _, _).
+
+// mtvar_class_param(Method, TypeVariable, Position): parameter Position is `Class`.
+mtvar_class_param(m, tv, pos) :- java_method_parameter(_, pos, m, _, _, _, _, _, _, _, _, _, param),
+ java_type_reference(_, "METHOD_PARAM", _, _, _, _, _, "0", "Class", _, _, _, _, _, _, param, "METHOD_PARAM", cref),
+ java_type_reference("TYPE_VARIABLE", _, _, _, _, cref, "0", "1", _, _, tv, _, _, _, _, _, _, _).
+mtvar_class_param(m, tv, pos) :- lib_method_parameter(_, pos, m, _, _, _, _, _, _, _, _, _, param),
+ lib_type_reference(_, "METHOD_PARAM", _, _, _, _, _, "0", "Class", _, _, _, _, _, _, param, "METHOD_PARAM", cref),
+ lib_type_reference("TYPE_VARIABLE", _, _, _, _, cref, "0", "1", _, _, tv, _, _, _, _, _, _, _).
+
+// class_literal_type(Expr, Prov, Type): the type a class literal names.
+class_literal_type(e, "client", t) :- java_expression("CLASS_LITERAL", _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, pqn, _, _, _, _, _, _, e),
+ pqn != "", java_type(_, pqn, _, _, _, _, _, _, _, _, _, _, _, t).
+class_literal_type(e, "lib", t) :- java_expression("CLASS_LITERAL", _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, pqn, _, _, _, _, _, _, e),
+ pqn != "", lib_type(_, pqn, _, _, _, _, _, _, _, _, _, _, _, t).
+class_literal_type(e, prov, t) :- java_expression("CLASS_LITERAL", _, _, _, _, _, _, _, _, _, name, _, _, _, _, _, _, pqn, _, _, _, _, _, _, e),
+ name != "", !type_qname_known(pqn),
+ expr_ultimate_type("client", e, encl),
+ java_type(_, _, _, _, _, _, _, file, _, _, _, _, _, encl),
+ type_resolves_in_file(prov, name, file, t).
+
+expr_type(prov, call, t) :- java_expression("CLASS_LITERAL", "ARGUMENT", _, _, _, _, call, pos, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, lit),
+ applicable_candidate(call, callee),
+ mtvar_ret(callee, tv),
+ mtvar_class_param(callee, tv, pos),
+ class_literal_type(lit, prov, t).
// ── NESTED generics (c4): `List> l; l.get(0).get()`. type_arg_binding only binds
// TOP-LEVEL (depth-1) args, so the inner Box.T=Dog is lost. arg_ref_at keys on the PARENT
diff --git a/graph/java/engine/resolution/local-flow.dl b/graph/java/engine/resolution/local-flow.dl
index 0d935863..8680ccab 100644
--- a/graph/java/engine/resolution/local-flow.dl
+++ b/graph/java/engine/resolution/local-flow.dl
@@ -33,6 +33,20 @@
// java_local_variable (19): 0 name 13 method 18 hash
// ============================================================================
+// A USE of a local: the LOCAL_VARIABLE reference joined to its declaration, ONCE, below the
+// typing fixpoint. These same three atoms sat inline in five recursive rules, and semi-naive
+// evaluation re-ran the full java_expression scan every iteration — 163 iterations on a large
+// subject, ~40% of the whole solve between them. Hoisted, each of those rules starts from its
+// recursive delta and probes this table.
+local_use(use, local) :-
+ java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, use),
+ expr_ultimate_method("client", use, m),
+ java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local).
+// ...and the uses whose local is initialized by a call, with that initializer.
+local_use_init(use, initExpr) :-
+ local_use(use, local),
+ java_expression("METHOD_INVOCATION", _, "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, initExpr).
+
// The initializer expression of a local (LOCAL_VAR_INITIALIZER root, owned by the local).
local_init_expr(local, e) :-
java_expression(_, "ROOT", "LOCAL_VAR_INITIALIZER", "LOCAL_VARIABLE", _, local, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, e).
@@ -53,7 +67,5 @@ local_flow_type(local, t) :- local_assign_value(local, e), expr_type(_, e, t).
// PROPAGATE: a LOCAL_VARIABLE receiver reference takes the flow-type(s) of its local
// (POSITIVE — resolves the concrete method directly; the cap handles the residual fan).
expr_type("client", recv, t) :-
- java_expression(_, _, _, _, _, _, _, _, _, _, name, _, _, _, "LOCAL_VARIABLE", _, _, _, _, _, _, _, _, _, recv),
- expr_ultimate_method("client", recv, m),
- java_local_variable(name, _, _, _, _, _, _, _, _, _, _, _, _, m, _, _, _, _, local),
+ local_use(recv, local),
local_flow_type(local, t).
diff --git a/graph/java/engine/resolution/type-arg-binding.dl b/graph/java/engine/resolution/type-arg-binding.dl
index da9f3a93..e9353f1d 100644
--- a/graph/java/engine/resolution/type-arg-binding.dl
+++ b/graph/java/engine/resolution/type-arg-binding.dl
@@ -93,6 +93,17 @@ super_tvar_rename(sub, super, superParam, subVar) :- type_super_ref_h(sub, super
lib_type_parameter(superParam, pos, _, _, _, _, super, _),
java_type_reference("TYPE_VARIABLE", _, _, _, _, sref, pos, "1", _, _, subVar, _, _, _, _, _, _, _).
+// Positional rename, client subtype -> CLIENT supertype (`class Mid extends Base`, both
+// in the source). The clauses above only reach a library supertype, because the substitution
+// downstream once read only library methods; java_method_ret_tvar and the implicit-receiver
+// clauses in generic-chain.dl read client ones too, and a binding that stopped one level up a
+// client hierarchy lost every member declared higher (#1478).
+super_tvar_rename(sub, super, superParam, subVar) :- type_super_ref_h(sub, superName, sref),
+ type_parent(sub, super),
+ java_type(superName, _, _, _, _, _, _, _, _, _, _, _, _, super),
+ java_type_parameter(superParam, pos, _, _, _, _, super, _),
+ java_type_reference("TYPE_VARIABLE", _, _, _, _, sref, pos, "1", _, _, subVar, _, _, _, _, _, _, _).
+
// Transitive climb: a binding on Sub carries to each Super via the positional rename,
// closing over the whole ancestor chain (bounded — the type hierarchy is a finite DAG).
type_arg_binding(ref, super, superParam, argType) :- type_arg_binding(ref, sub, subVar, argType),
diff --git a/graph/java/souffle/decls_all.dl b/graph/java/souffle/decls_all.dl
index 625f6581..605f86b2 100644
--- a/graph/java/souffle/decls_all.dl
+++ b/graph/java/souffle/decls_all.dl
@@ -35,6 +35,7 @@
.decl entry_reachable(c0:symbol)
.decl http_entry_ann(c0:symbol)
.decl junit_entry_ann(c0:symbol)
+.decl annotation_type_category(c0:symbol)
.decl cast_type_resolves(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl client_calls_client(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl client_calls_lib(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -463,6 +464,7 @@
.decl lib_dispatch_width_low(c0:symbol,c1:number)
.decl lib_wide_dispatch(c0:symbol)
.decl dispatch_cap(c0:symbol)
+.decl dispatch_closed_world(c0:symbol)
.decl field_final(c0:symbol)
.decl field_init_new_type(c0:symbol,c1:symbol)
.decl field_multi_new(c0:symbol)
@@ -473,6 +475,8 @@
.decl cha_wide_base(c0:symbol)
.decl cha_override_low(c0:symbol,c1:symbol)
.decl local_init_expr(c0:symbol,c1:symbol)
+.decl local_use(c0:symbol,c1:symbol)
+.decl local_use_init(c0:symbol,c1:symbol)
.decl local_assign_value(c0:symbol,c1:symbol)
.decl local_flow_type(c0:symbol,c1:symbol)
.decl param_flow_type(c0:symbol,c1:symbol)
@@ -488,6 +492,12 @@
.decl concrete_class_type(c0:symbol)
.decl lib_method_ret_param(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl expr_arg_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl class_tvar_binding(c0:symbol,c1:symbol,c2:symbol,c3:symbol) // (type, super, superParam, arg) as seen from type
+.decl field_tvar(c0:symbol,c1:symbol,c2:symbol) // (field, ownerType, typeVariable) a field typed as its owner's variable
+.decl mtvar_ret(c0:symbol,c1:symbol) // (method, its own type variable it returns)
+.decl mtvar_class_param(c0:symbol,c1:symbol,c2:symbol) // (method, typeVariable, position of its Class parameter)
+.decl class_literal_type(c0:symbol,c1:symbol,c2:symbol) // (classLiteralExpr, prov, type it names)
+.decl ref_names_tvar(c0:symbol) // a reference whose name is a type variable in scope
.decl type_fixes_super(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl arg_ref_at(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
@@ -753,6 +763,76 @@
.decl grpc_impl_holder(c0:symbol,c1:symbol)
.decl grpc_serves(c0:symbol,c1:symbol)
.decl grpc_sends_at(c0:symbol,c1:symbol,c2:symbol)
+// gRPC with the generated holder outside the source tree (#1548): the same pairing from written names
+.decl grpc_generated_suffix(c0:symbol)
+.decl grpc_ext_name_demand(c0:symbol,c1:symbol)
+.decl grpc_ext_dot(c0:symbol,c1:number)
+.decl grpc_ext_last_dot(c0:symbol,c1:number)
+.decl grpc_ext_prev_dot(c0:symbol,c1:number)
+.decl grpc_ext_has_prev(c0:symbol)
+.decl grpc_ext_parts(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl grpc_ext_service(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_ext_server(c0:symbol)
+.decl grpc_ext_client(c0:symbol)
+.decl grpc_ext_impl_site(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_ext_send_site(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
+.decl grpc_ext_site(c0:symbol,c1:symbol)
+.decl grpc_file_on_demand(c0:symbol)
+.decl grpc_file_package(c0:symbol,c1:symbol)
+.decl grpc_ext_holder_q(c0:symbol,c1:symbol,c2:symbol)
+.decl grpc_rpc_shaped(c0:symbol)
+.decl grpc_ext_handler(c0:symbol,c1:symbol,c2:symbol)
+// framework entry points and HTTP destinations (#1400 #1410 #1428-#1430 #1456-#1458 #1463 #1464)
+.decl cfg_jpa_callback_ann(c0:symbol)
+.decl jpa_callback_owner(c0:symbol)
+.decl cfg_jaxrs_param_ann(c0:symbol)
+.decl cfg_jaxrs_param_factory(c0:symbol)
+.decl cfg_servlet_registrar_type(c0:symbol)
+.decl cfg_xml_servlet_name_elem(c0:symbol)
+.decl cfg_xml_servlet_mapping_elem(c0:symbol)
+.decl cfg_xml_url_pattern_elem(c0:symbol)
+.decl cfg_servlet_url_arg(c0:symbol)
+.decl cfg_jaxrs_client_owner(c0:symbol)
+.decl cfg_jaxrs_builder_step(c0:symbol)
+.decl cfg_subresource_locator_ann(c0:symbol)
+.decl servlet_registrar_recv(c0:symbol)
+.decl jaxrs_param_type(c0:symbol)
+.decl string_param_0(c0:symbol)
+.decl jaxrs_string_type(c0:symbol)
+.decl path_ends_slash(c0:symbol)
+.decl path_starts_slash(c0:symbol)
+.decl path_ends_other(c0:symbol)
+.decl path_starts_other(c0:symbol)
+.decl cfg_bean_lifecycle_arg(c0:symbol,c1:symbol)
+.decl cfg_registering_type_ann(c0:symbol,c1:symbol)
+.decl cfg_registered_callback_name(c0:symbol,c1:symbol)
+.decl cfg_servlet_registration(c0:symbol,c1:symbol)
+.decl cfg_servlet_registration_bean(c0:symbol,c1:symbol)
+.decl cfg_servlet_verb_method(c0:symbol,c1:symbol)
+.decl cfg_jaxrs_invoke_verb(c0:symbol,c1:symbol)
+.decl object_creation_named(c0:symbol,c1:symbol)
+.decl registered_arg_type(c0:symbol,c1:symbol)
+.decl path_join_demand(c0:symbol,c1:symbol)
+.decl jaxrs_locator(c0:symbol,c1:symbol)
+.decl locator_base(c0:symbol,c1:symbol)
+.decl servlet_pattern(c0:symbol,c1:symbol)
+.decl servlet_route_raw(c0:symbol,c1:symbol)
+.decl jaxrs_target(c0:symbol,c1:symbol)
+.decl jaxrs_builder(c0:symbol,c1:symbol)
+.decl http_url_arg(c0:symbol,c1:symbol)
+.decl cfg_registering_class_arg(c0:symbol,c1:symbol,c2:symbol)
+.decl cfg_ann_name_of(c0:symbol,c1:symbol,c2:symbol)
+.decl route_raw_own(c0:symbol,c1:symbol,c2:symbol)
+.decl path_join(c0:symbol,c1:symbol,c2:symbol)
+.decl servlet_decl_name(c0:symbol,c1:symbol,c2:symbol)
+.decl servlet_mapped_pattern(c0:symbol,c1:symbol,c2:symbol)
+.decl servlet_handler(c0:symbol,c1:symbol,c2:symbol)
+.decl jaxrs_send(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl typed_simple_demand(c0:symbol)
+.decl recv_type_simple(c0:symbol,c1:symbol)
+.decl jaxrs_client_root(c0:symbol)
+.decl locator_only_resource(c0:symbol)
+.decl cfg_jaxrs_client_factory(c0:symbol)
.decl sends_destination(c0:symbol,c1:symbol,c2:symbol)
.decl route_base(c0:symbol,c1:symbol)
.decl route_base_declared(c0:symbol)
@@ -849,6 +929,24 @@
.decl pf_nested(c0:symbol,c1:symbol)
.decl pf_direct(c0:symbol,c1:symbol)
.decl persistence_unresolved(c0:symbol,c1:symbol)
+.decl cfg_derived_query_prefix(c0:symbol)
+.decl repo_entity(c0:symbol,c1:symbol)
+.decl repo_entity_field(c0:symbol,c1:symbol,c2:symbol)
+.decl repo_field_cap(c0:symbol,c1:symbol,c2:symbol)
+.decl has_method_query_ann(c0:symbol)
+.decl derived_candidate(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
+.decl derived_by_at(c0:symbol,c1:number)
+.decl derived_criteria(c0:symbol,c1:symbol,c2:symbol)
+.decl derived_start(c0:symbol,c1:number)
+.decl derived_joiner(c0:symbol)
+.decl derived_match(c0:symbol,c1:number,c2:number,c3:symbol)
+.decl derived_ends(c0:symbol,c1:number)
+.decl derived_longest(c0:symbol,c1:number,c2:number)
+.decl pf_derived(c0:symbol,c1:symbol)
+.decl orm_named_query(c0:symbol,c1:symbol,c2:symbol)
+.decl cfg_orm_named_query_elem(c0:symbol)
+.decl cfg_xml_param_marker_root(c0:symbol)
+.decl xml_param_marker_file(c0:symbol)
.decl cfg_method_query_ann(c0:symbol)
.decl cfg_query_arg(c0:symbol)
.decl query_padded(c0:symbol,c1:symbol)
@@ -892,12 +990,9 @@
.decl gen_prefix(c0:symbol,c1:symbol) // (prov,accessorPrefix)
.decl gen_prefix_ambiguous(c0:symbol)
.decl gen_prefix_applies(c0:symbol,c1:symbol,c2:symbol)
-.decl gen_prefix_key(c0:symbol)
.decl gen_prefix_raw(c0:symbol,c1:symbol)
-.decl gen_prefix_value(c0:symbol,c1:symbol)
.decl gen_pfx_junk(c0:symbol)
.decl gen_pfx_step(c0:symbol,c1:symbol)
-.decl gen_accessor_src(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol) // (prov,owner,fieldName,fieldHash,kind)
.decl gen_return_type(c0:symbol,c1:symbol,c2:symbol) // (generatedGetterId, typeRef, type)
.decl gen_capitalized(c0:symbol,c1:symbol) // (name, Name)
.decl gen_accessor(c0:symbol,c1:symbol,c2:symbol,c3:symbol) // (prov,owner,methodName,arity)
@@ -912,6 +1007,45 @@
.decl cfg_lib_bean_narrowing_enabled()
.decl cfg_staged_lib_type(c0:symbol)
.decl cfg_injectable_scope_type(c0:symbol)
+.decl gen_field_with_ann(c0:symbol) // annotation on a FIELD declaring a wither
+.decl gen_type_with_ann(c0:symbol) // annotation on a TYPE declaring a wither per field
+.decl gen_logger_type(c0:symbol,c1:symbol) // (logger annotation, qualified type of the field)
+.decl gen_static_arg(c0:symbol,c1:symbol) // (constructor annotation, element naming its static factory)
+.decl gen_acc(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol) // (prov,owner,fieldName,fieldType,fieldHash,kind)
+.decl gen_cfg_key(c0:symbol) // a lombok.config key read here
+.decl gen_cfg_value(c0:symbol,c1:symbol,c2:symbol) // (prov,key,rawValue)
+.decl gen_cfg_clean(c0:symbol,c1:symbol,c2:symbol) // (prov,key,value with += / = peeled)
+.decl gen_style_opt(c0:symbol) // an @Accessors element read here
+.decl gen_acc_ann_field(c0:symbol,c1:symbol,c2:symbol) // (fieldHash,option,value) from @Accessors on the field
+.decl gen_acc_ann_type(c0:symbol,c1:symbol,c2:symbol) // (ownerType,option,value) from @Accessors on the type
+.decl gen_acc_field_has(c0:symbol,c1:symbol)
+.decl gen_acc_type_has(c0:symbol,c1:symbol)
+.decl gen_style(c0:symbol,c1:symbol,c2:symbol) // (fieldHash,option,value) as the processor decides it
+.decl gen_fluent(c0:symbol) // fieldHash whose accessors are fluent
+.decl gen_chain(c0:symbol) // fieldHash whose setter returns the owner
+.decl gen_is_stripped(c0:symbol,c1:symbol,c2:symbol) // (prov,fieldHash,name) boolean isX field -> X
+.decl gen_prop(c0:symbol,c1:symbol,c2:symbol) // (prov,fieldHash,propertyName)
+.decl gen_cap_demand(c0:symbol)
+.decl gen_acc_named(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol) // (prov,owner,fieldHash,kind,methodName,arity)
+.decl gen_member(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol) // (prov,owner,ownerQualifiedName,name,arity)
+.decl gen_field_type_q(c0:symbol,c1:symbol) // (generatedFieldId, qualified type name)
+.decl gen_field_type(c0:symbol,c1:symbol,c2:symbol) // (generatedFieldId, prov, type)
+.decl gen_field_recv(c0:symbol,c1:symbol) // (receiverExpr, generatedFieldId)
+.decl gen_ctor_static(c0:symbol,c1:symbol,c2:symbol,c3:symbol) // (prov,owner,shape,factoryName)
+.decl gen_ctor_arity(c0:symbol,c1:symbol,c2:number) // (owner,shape,arity)
+.decl gen_ret_self(c0:symbol,c1:symbol) // (generatedMethodId, type it returns, no IR reference)
+.decl gen_builder(c0:symbol,c1:symbol,c2:symbol) // (prov,owner,annotationHash)
+.decl gen_builder_opt(c0:symbol,c1:symbol,c2:symbol) // (owner,element,value)
+.decl gen_builder_opt_has(c0:symbol,c1:symbol)
+.decl gen_builder_name(c0:symbol,c1:symbol,c2:symbol) // (owner, class|method|build, name)
+.decl gen_builder_written(c0:symbol,c1:symbol,c2:symbol) // (owner, builderType, qname) the source writes the builder
+.decl gen_builder_has_written(c0:symbol)
+.decl gen_builder_type(c0:symbol,c1:symbol,c2:symbol,c3:symbol) // (prov,owner,builderType,builderQualifiedName)
+.decl gen_builder_field_skip(c0:symbol)
+.decl gen_builder_field(c0:symbol,c1:symbol,c2:symbol) // (prov,owner,fieldName) with a builder setter
+.decl gen_delegate(c0:symbol,c1:symbol,c2:symbol) // (prov,owner,delegateFieldType)
+.decl gen_delegate_kind(c0:symbol)
+.decl gen_delegate_target(c0:symbol,c1:symbol,c2:symbol,c3:symbol) // (delegateFieldType,name,arity,method)
.decl gen_accessor_field(c0:symbol,c1:symbol)
// call-edge-generation/event_dispatch.dl + knobs.dl (20): Spring application events (#1391)
.decl cfg_event_publisher(c0:symbol,c1:symbol)
@@ -966,3 +1100,26 @@
.decl twr_close_decl(c0:symbol,c1:symbol)
.decl twr_close(c0:symbol,c1:symbol)
.decl twr_close_count(c0:symbol,c1:number)
+// call-edge-generation/callback_dispatch.dl
+.decl cb_object_member(c0:symbol)
+.decl cb_arg_type(c0:symbol,c1:symbol)
+.decl cb_site_kind(c0:symbol)
+.decl cb_client_site(c0:symbol)
+.decl cb_handoff(c0:symbol,c1:symbol)
+.decl cb_member(c0:symbol,c1:symbol,c2:symbol)
+.decl cb_callback(c0:symbol,c1:symbol)
+.decl cb_external_ancestor(c0:symbol)
+.decl cb_writes_override(c0:symbol)
+.decl cb_marker_type(c0:symbol)
+.decl cb_direct_external(c0:symbol)
+// call-edge-generation/exception_handlers.dl + knobs.dl (22): exception handler methods
+.decl cfg_exh_ann(c0:symbol)
+.decl cfg_exh_arg(c0:symbol)
+.decl cfg_exh_advice_ann(c0:symbol)
+.decl exh_ann_method(c0:symbol,c1:symbol)
+.decl exh_names_classes(c0:symbol)
+.decl exh_handles(c0:symbol,c1:symbol)
+.decl exh_global(c0:symbol)
+.decl exh_built(c0:symbol,c1:symbol,c2:symbol)
+.decl exh_handles_built(c0:symbol,c1:symbol)
+.decl exh_edge(c0:symbol,c1:symbol,c2:symbol)
diff --git a/graph/javascript/README.md b/graph/javascript/README.md
index 6a26b2ab..1336eecd 100644
--- a/graph/javascript/README.md
+++ b/graph/javascript/README.md
@@ -103,6 +103,7 @@ arrays. That is the engine, entirely.
| module graph | `resolution/module-graph.dl` | one export surface for both systems, keyed by name with `default` for `module.exports = X`; a CommonJS default value's properties ARE its members |
| hierarchy | `resolution/type-hierarchy.dl` | ONE closure — every heritage form inherits members, there is no `implements` |
| value flow | `resolution/value-flow.dl` | the may-analysis above |
+| instance state | `resolution/instance-state.dl` | an `("alloc", new-expression)` value beside `("inst", T)`, and the facts that let impact walk a callback or dependency given to ONE instance (a constructor option, a subscription) only from callers whose receiver may be that instance |
| arrays | `resolution/arrays.dl` | the one platform type modelled: `push`, `[i]`, `map`, `forEach`, `for..of`, `T[]`; `Map` / `Set` as collections, including an instance of a class that extends one (#619) |
| ambient | `resolution/ambient.dl` | platform names as values, so a site reached through one is classified from the value, not the syntax |
| JSDoc types | `resolution/reference-types.dl` | `@param`/`@type`/`@returns`, `import()` types, typedef aliases, wrappers |
diff --git a/graph/javascript/engine/call-edge-generation/callbacks.dl b/graph/javascript/engine/call-edge-generation/callbacks.dl
index 107842db..1ab0ee16 100644
--- a/graph/javascript/engine/call-edge-generation/callbacks.dl
+++ b/graph/javascript/engine/call-edge-generation/callbacks.dl
@@ -28,18 +28,59 @@
// ============================================================================
// ── callback_registered(CallExpr, CallbackMethod) ───────────────────────────
-callback_registered(ce, m) :- invocation_site(ce, _), call_arg(ce, _, arg), expr_value(arg, "func", m).
+callback_registered(ce, m) :- invocation_site(ce, _), !call_has_client_target(ce), !collection_store_site(ce),
+ call_arg(ce, _, arg), expr_value(arg, "func", m).
+// A PROJECT callee's own body calls what it is handed, so the edge from outside restates
+// it — and only for a function WRITTEN at the argument. A parameter or loop variable passed
+// on holds whatever the value flow says it may, and `for (const [pattern, handlers] of map)`
+// gives the key every value too: `matches(pattern)` would register every handler.
+callback_registered(ce, m) :- invocation_site(ce, _), call_has_client_target(ce), call_arg(ce, _, arg),
+ written_function(arg, m).
+// written_function(Expr, Method): the expression names the function where it is written —
+// a function or arrow literal, a function declaration or an import of one, a variable a
+// literal initialises, a method read off a value (`this.onData`), or a call that returns
+// one (`once(memoize(f))`, `fn.bind(this)`).
+written_function(e, m) :- expr_introduces(_, m, e).
+written_function(e, m) :- expr_kind(_, "IDENTIFIER", _, e), expr_binding(_, v, e), function_binding_method(v, m).
+written_function(e, m) :- expr_kind(_, "IDENTIFIER", _, e), expr_binding(_, v, e), var_import(_, imp, v),
+ import_value(imp, "func", m).
+written_function(e, m) :- expr_kind(_, "IDENTIFIER", _, e), expr_binding(_, v, e), var_init(_, _, i, v),
+ written_function(i, m).
+written_function(e, m) :- expr_kind(_, "PROPERTY_ACCESS", _, e), expr_value(e, "func", m).
+written_function(e, m) :- expr_kind(_, "CALL", _, e), expr_value(e, "func", m).
+// A Map or Set stores, finds and drops its argument and never calls it (`subs.add(h)`,
+// `cache.get(key)`, `seen.has(entry)`); only `forEach` runs what it is handed. The
+// function is reached where it is taken back out and called, not where it is stored.
+collection_store_site(ce) :- call_site(_, ck, mn, "SYNTACTIC", _, _, ce, _, _), call_kind_is_member_form(ck), mn != "forEach",
+ expr_child(_, ce, "RECEIVER", _, r), expr_value(r, "coll", _).
// A function handed over as a PROPERTY of an options object (`lib({ filter: f })`,
// `opts.resolve = f; lib(opts)`, `https.request({ createConnection: f })`,
// `new Transform({ transform })`) is a callback too (#643): the callee reads the property
-// and invokes it, and nothing else in the project points into the function. Followed
-// two levels (`{ hooks: { visit } }`), and only across the boundary: a callee that is
-// NOT a project function (a staged library, the platform, an unknown, a dynamic call),
-// because a project callee that reads the property has the ordinary edge from inside
-// its body. Only object values carry it (below); an array, a collection, a string, a
-// module, an instance and the platform are left out.
+// and invokes it, and nothing else in the project points into the function. Only across
+// the boundary: a callee that is NOT a project function (a staged library, the platform,
+// an unknown, a dynamic call), because a project callee that reads the property has the
+// ordinary edge from inside its body.
+// Read off the SOURCE, not the value flow: the argument is an object literal or a variable
+// one initialises, and the function is WRITTEN into it — a property value, a method of the
+// literal, an assignment to a property of that variable, or the same one literal deeper
+// (`{ hooks: { visit } }`). An argument whose abstract value merely MAY be such an object
+// (a parameter, a property read, a call's result, a string key the flow over-approximated)
+// hands nothing over, nor does a member holding a parameter or loop variable:
+// `assert.equal(cfg.port, 1)`, `cache.get(key)` and `emit('error', { pattern })` would
+// otherwise register every function of every object that value might be.
callback_registered(ce, m) :- invocation_site(ce, _), !call_has_client_target(ce), !reflective_site(ce),
- call_arg(ce, _, arg), expr_value(arg, k, i), options_value_kind(k), options_member_func(k, i, m).
+ !collection_store_site(ce), call_arg(ce, _, arg), options_argument(arg, l), options_written_member(l, m).
+options_argument(l, l) :- expr_kind(_, "OBJECT_LITERAL", _, l).
+options_argument(arg, l) :- expr_kind(_, "IDENTIFIER", _, arg), expr_binding(_, v, arg), var_init(_, _, l, v),
+ expr_kind(_, "OBJECT_LITERAL", _, l).
+options_written_member(l, m) :- expr_kind(_, "OBJECT_LITERAL", _, l), literal_owns_method(l, m),
+ method_decl(_, _, k, _, _, _, _, m), !method_kind_is_accessor(k).
+options_written_member(l, m) :- expr_child(_, l, "PROPERTY_VALUE", _, v), options_written_value(v, m).
+options_written_member(l, m) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, t),
+ expr_kind(_, "PROPERTY_ACCESS", _, t), expr_child(_, t, "ACCESS_TARGET", _, r), expr_kind(_, "IDENTIFIER", _, r),
+ options_argument(r, l), expr_child(_, a, "ASSIGNMENT_VALUE", _, v), options_written_value(v, m).
+options_written_value(v, m) :- written_function(v, m).
+options_written_value(v, m) :- options_argument(v, l), options_written_member(l, m).
// `Object.assign(dst, src)`, `Object.entries(o)`, `Object.setPrototypeOf(a, b)`,
// `Reflect.ownKeys(o)`, `JSON.stringify(o)`: reflection over an object reads its
// properties and never invokes them; an options-object edge there would be invented.
@@ -49,23 +90,37 @@ reflective_ambient("Object").
reflective_ambient("Reflect").
reflective_ambient("JSON").
reflective_ambient("Array").
-// Two levels, each side materialised WITHOUT the name wildcard (#671): joining
-// prop_value with itself through `_` in the name column left neither side a
-// prefix index for the other's lookup, and on lodash (26M prop_value tuples after
-// its mixin copies every function onto every object) that one join took 43 of a
-// 51-minute solve to produce 16.8K rows. The projections below are deduplicated
-// per (object, member value), and each join is a prefix lookup.
-options_member_func(k, i, m) :- options_member_func_own(k, i, m).
-options_member_func(k, i, m) :- options_object_member(k, i, k1, i1), options_member_func_own(k1, i1, m).
-options_member_func_own(k, i, m) :- prop_value(k, i, _, "func", m).
-options_object_member(k, i, k1, i1) :- prop_value(k, i, _, k1, i1), options_value_kind(k1).
-// Object values only: an INSTANCE passed to the platform is data (`arr.push(this)`,
-// `Promise.resolve(w)`, `items.map(fn, this)`), and registering every method it has
-// would invent an edge per method at every such site.
-options_value_kind("obj").
call_has_client_target(ce) :- expr_resolves_to_method(ce, m), method_prov(m, "client").
call_has_client_target(ce) :- new_constructs(ce, t), type_decl("client", _, _, _, _, t).
+// A function WRAPPED BY A PACKAGE CALL and kept in a binding: `export const plugin = fp(async (app) => …)`,
+// `const ext = Prisma.defineExtension((client) => …)`, then `app.register(plugin)`, `client.$extends(ext)`.
+// The package returns the function it was handed, or one that runs it, and nothing in the project says
+// which, so the binding had no value and the registration reached nothing, although the same literal
+// handed to it bare is registered. The wrapping site is a value of its own, ("libwrap", site), carried
+// wherever a value goes (a const, an import, an export), and a registration handed it registers what the
+// site was handed. The site is recognised syntactically, by a callee rooted at a binding imported from a
+// package, so the value stays below the resolver's negations; a project wrapper is followed through its
+// body instead (value-flow.dl, "wrap").
+lib_rooted(e) :- expr_kind(_, "IDENTIFIER", _, e), expr_binding(_, v, e), var_import(_, imp, v),
+ import_decl(_, _, _, _, _, _, out, _, imp), import_outcome_is_package(out).
+lib_rooted(e) :- expr_kind(_, k, _, e), access_kind_reads_member(k), expr_child(_, e, "ACCESS_TARGET", _, r), lib_rooted(r).
+import_outcome_is_package("RESOLVED_EXTERNAL").
+import_outcome_is_package("UNRESOLVED_MISSING").
+access_kind_reads_member("PROPERTY_ACCESS").
+access_kind_reads_member("OPTIONAL_ACCESS").
+lib_wrap_site(s) :- call_site(_, ck, _, _, _, _, s, _, _), call_kind_is_callee_form(ck),
+ expr_child(_, s, "CALLEE", _, c), lib_rooted(c).
+lib_wrap_site(s) :- call_site(_, ck, _, "SYNTACTIC", _, _, s, _, _), call_kind_is_member_form(ck),
+ expr_child(_, s, "RECEIVER", _, r), lib_rooted(r).
+// Handed over by NAME: `register(wrap(f))` needs nothing new, since the inner site already registers f
+// from the same caller. The value is not a callee (callee-resolution.dl): what a call of it runs is still
+// unknown, and says so.
+expr_value(s, "libwrap", s) :- lib_wrap_site(s), call_arg(s, _, a), expr_value(a, "func", _).
+callback_registered(ce, m) :- invocation_site(ce, _), !call_has_client_target(ce), !reflective_site(ce),
+ call_arg(ce, _, arg), !expr_kind(_, "CALL", _, arg),
+ expr_value(arg, "libwrap", s), call_arg(s, _, a), expr_value(a, "func", m).
+
// A listener runs with the EMITTER as `this` (`e.on('x', function () { this.other(); })`).
this_value(m, k, i) :- event_handler(k, i, _, m), method_this_binding(_, "DYNAMIC", m), !method_owner_type(m, _), k != "module".
diff --git a/graph/javascript/engine/call-edge-generation/calls.dl b/graph/javascript/engine/call-edge-generation/calls.dl
index 34fbd6b8..5745b29d 100644
--- a/graph/javascript/engine/call-edge-generation/calls.dl
+++ b/graph/javascript/engine/call-edge-generation/calls.dl
@@ -72,6 +72,36 @@ unresolved_covered(ce) :- call_site("client", ck, _, _, _, _, ce, _, _), call_ki
unresolved_receiver(ce, cn, ck, "other_form") :- call_site("client", ck, cn, _, _, _, ce, _, _),
!call_resolved(ce), !unresolved_covered(ce).
+// ── library_receiver(CallExpr, Specifier) ───────────────────────────────────
+// An unresolved member call whose receiver is a PACKAGE'S value: a binding imported
+// from a package no staged IR declares, a member of one, what calling or constructing
+// one returns, and a const holding any of those — `request(app).get('/x').expect(200)`,
+// `new grpc.Metadata().get(k)`, `const md = new Metadata(); md.get(k)`. The receiver's
+// origin is known, so the site is not an untyped `x.get()`: a project method of the
+// same name is not what it calls. It stays unresolved (the package is not staged), and
+// consumers list it apart from the name matches on unknown receivers rather than
+// among them. Read off the source, never the value flow, so it cannot invent a value.
+// A call handed a project INSTANCE is left out: a wrapper, a proxy or a mock helper
+// may return the very object it was given (`mocked(repo).get(1)`).
+// A package is named by a bare specifier: a relative or absolute path that reached no
+// file (`require('./not-here')`) is a missing PROJECT module, and an alias (`@/x`, `~/x`,
+// `#x`) may map onto the project's own source.
+package_import(imp, spec) :- import_decl(_, spec, _, _, _, _, out, _, imp), import_outcome_is_package(out),
+ !match("[.#~/].*", spec), !match("@/.*", spec).
+library_value(e, spec) :- expr_kind(_, "IDENTIFIER", _, e), expr_binding(_, v, e), var_import(_, imp, v),
+ package_import(imp, spec).
+library_value(e, spec) :- expr_kind(_, "MODULE_EDGE_CALL", _, e), expr_module_edge(_, imp, e), package_import(imp, spec).
+library_value(e, spec) :- expr_kind(_, k, _, e), access_kind_reads_member(k), expr_child(_, e, "ACCESS_TARGET", _, r),
+ library_value(r, spec).
+library_value(e, spec) :- expr_kind(_, k, _, e), (k = "CALL" ; k = "NEW"), expr_child(_, e, role, _, c),
+ (role = "CALLEE" ; role = "RECEIVER"), library_value(c, spec), !call_passes_project_instance(e).
+library_value(e, spec) :- expr_kind(_, "AWAIT", _, e), expr_child(_, e, _, _, c), library_value(c, spec).
+library_value(e, spec) :- expr_kind(_, "IDENTIFIER", _, e), expr_binding(_, v, e), !var_import(_, _, v),
+ var_init("client", _, i, v), !variable_reassigned(v), library_value(i, spec).
+call_passes_project_instance(e) :- call_arg(e, _, a), expr_value(a, "inst", _).
+library_receiver(ce, spec) :- call_site("client", ck, _, "SYNTACTIC", _, _, ce, _, _), call_kind_is_member_form(ck),
+ !call_resolved(ce), expr_child(_, ce, "RECEIVER", _, r), library_value(r, spec).
+
// ── unresolved_value_callee(CallExpr, Binding) ──────────────────────────────
// An unresolved call whose callee is a VALUE, not a name: `cb()` on a parameter,
// `f()` on a loop variable or a local, `table[k]()`, `eval(s)`. Nothing about its
@@ -107,8 +137,62 @@ module_variable_from_call(v) :- var_init("client", _, e, v), expr_kind(_, k, _,
!variable_reassigned(v), !call_passes_function(e).
call_passes_function(e) :- call_arg(e, _, a), expr_value(a, "func", _).
call_passes_function(e) :- call_arg(e, _, a), expr_introduces(_, _, a).
+// `promisify(store.find.bind(store))`: a `.bind` always evaluates to a function, typed
+// receiver or not. A bound platform function (`Math.max.bind(Math)`) is the platform's.
+call_passes_function(e) :- call_arg(e, _, a), call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, a, _, _),
+ expr_child(_, a, "CALLEE", _, f), !bound_platform_function(f).
+bound_platform_function(f) :- expr_kind(_, "PROPERTY_ACCESS", _, f), expr_child(_, f, "ACCESS_TARGET", _, r),
+ expr_value(r, "ambient", _).
+// A holder whose value is what a PLATFORM call returned when handed a project function
+// (`this.find = promisify(s.find.bind(s))`, `const f = util.callbackify(g)`): the value
+// is a wrapper the platform made around that function, and calling it runs the
+// function. The platform value on the holder made the call an ambient terminal, a
+// claimed correct end, and path said "independent" of the very method the wrapper
+// runs. A platform value made from no project function (`promisify(setTimeout)`)
+// keeps its platform reading.
+// When the graph knows the function the wrapper was made from (a function value, or
+// the method a `.bind` site resolves to), the call runs it: one of a set, beside the
+// platform row the call keeps. Only when it knows none is the callee an open value.
+unresolved_value_callee(ce, "field") :- wrapper_holder_call(ce, val), !wrapper_call_target(ce, _), made_from_function(val),
+ field_call(ce, _, _, _).
+unresolved_value_callee(ce, "module_variable") :- wrapper_holder_call(ce, e), !wrapper_call_target(ce, _), made_from_function(e),
+ !field_call(ce, _, _, _).
+wrapper_holder_call(ce, val) :- field_call(ce, k, t, n), !call_resolved(ce), field_assignment(k, t, n, val).
+wrapper_holder_call(ce, e) :- value_callee_unresolved(ce, c), expr_binding(_, v, c),
+ var_decl("client", _, _, _, _, _, v), !var_owner_method("client", _, v), !var_import(_, _, v), !expr_param(_, _, c),
+ var_init("client", _, e, v), !variable_reassigned(v).
+made_from_function(e) :- expr_kind(_, "CALL", _, e), expr_value(e, "ambient", _), call_passes_function(e).
+wrapper_runs(e, m) :- made_from_function(e), call_arg(e, _, a), expr_value(a, "func", m).
+wrapper_runs(e, m) :- made_from_function(e), call_arg(e, _, a), call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, a, _, _),
+ expr_resolves_to_method(a, m).
+wrapper_call_target(ce, m) :- wrapper_holder_call(ce, e), wrapper_runs(e, m), call_target_count(ce, 0).
+call_chain_edge(ce, caller, "-", m, "client", "multi_inferred", kind) :-
+ wrapper_call_target(ce, m), !call_over_cap(ce), call_from(ce, caller), invocation_site(ce, kind).
variable_reassigned(v) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt),
expr_binding(_, v, tgt).
+// ── a call of what a PROJECT wrapper returned runs what that site was handed ──
+// `export default promisable(eachItem, 3)`, `const add = curry2(function add(a, b) {…})`:
+// the wrapper returns a closure that calls its parameter, and every export of the module
+// is that closure. The closure's call of the parameter is read context-insensitively, so
+// it held every function any of the wrapper's sites passed (past the fan cap: nothing),
+// and a test calling the export reached the closure and stopped there. The site is
+// already a value of its own, ("wrap", site), beside the closure (value-flow.dl), and a
+// call through it takes its RESULT from that site's argument alone; the same site says
+// what the call RUNS: the function it was handed at the forwarded position, what the
+// wrapper writes into that parameter, or what a wrapped argument itself runs
+// (`memoize(once(f))`). One of a set, beside the edge to the closure the call keeps, and
+// under the same fan cap: a callee that may hold more wrapped values than --dispatch-cap
+// (a parameter every wrapped export is passed to) runs one of them, and naming all of
+// them says nothing.
+wrap_runs(s, g) :- wrap_site_arg(s, _, arg), expr_value(arg, "func", g).
+wrap_runs(s, g) :- wrap_site_arg(s, _, arg), expr_value(arg, "wrap", s2), wrap_runs(s2, g).
+wrap_runs(s, g) :- wrap_site(s, h, pos), wrapper_param_written(h, pos, o), expr_value(o, "func", g).
+wrapped_call_target(ce, g) :- callee_value(ce, "wrap", s), wrap_runs(s, g), method_prov(g, "client"),
+ !call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, ce, _, _).
+wrapped_target_count(ce, n) :- wrapped_call_target(ce, _), n = count : { wrapped_call_target(ce, _) }.
+wrapped_over_cap(ce) :- wrapped_target_count(ce, n), dispatch_cap(c), n > to_number(c).
+call_chain_edge(ce, caller, "-", g, "client", "multi_inferred", kind) :-
+ wrapped_call_target(ce, g), !call_over_cap(ce), !wrapped_over_cap(ce), call_from(ce, caller), invocation_site(ce, kind).
// `(c ? a : b)()`, `(0, cb)()`, `make()()`: the callee is computed by an expression.
// `new Function(s)()` / `Function(s)()` runs code built at run time.
unresolved_value_callee(ce, "dynamic_code") :- value_callee_unresolved(ce, c), callee_builds_code(c).
diff --git a/graph/javascript/engine/call-edge-generation/field_access.dl b/graph/javascript/engine/call-edge-generation/field_access.dl
new file mode 100644
index 00000000..066a5aee
--- /dev/null
+++ b/graph/javascript/engine/call-edge-generation/field_access.dl
@@ -0,0 +1,60 @@
+// ============================================================================
+// CALL-EDGE-GEN · A MODULE VARIABLE READ BY NAME, AS A DATA EDGE
+//
+// `CFG.name`, `use(SCHEMA)`, `${TOKENS.store}`: the identifier is bound by the binder
+// (expr_binding) to a variable -- the module's own top-level one, or an import binding.
+// This engine follows an import to the VALUE the export holds (import_value), which is
+// what a call needs, and never to the exported VARIABLE, which is what "who reads this
+// const" needs. So a read of an imported const was answered by name, and the name
+// matched every same-named const in every sibling package.
+//
+// The export surface is walked here once more, keeping the variable: export_decl names
+// it (`export const x` and `export { x }` carry target kind VARIABLE), and a re-export
+// (`export * from`, `export { x } from`) passes it on exactly as module_export_value
+// passes a value on. A variable holding a function or a class is left out: its uses
+// are calls and constructions, which call edges already carry.
+//
+// field_access(Site, Caller, Variable, "client", "known_edge", Access), the shape the
+// Java and TypeScript engines export, so the bundle reads one relation for all three.
+// ============================================================================
+
+js_module_var(v) :- var_decl("client", _, reg, _, _, _, v), !var_owner_method("client", _, v),
+ !js_not_a_data_binding(reg),
+ !var_init("client", "FUNCTION", _, v), !var_init("client", "CLASS", _, v),
+ !var_import("client", _, v).
+js_not_a_data_binding("IMPORT_BINDING").
+js_not_a_data_binding("FUNCTION_DECLARATION_HOISTED").
+js_not_a_data_binding("CLASS_TDZ").
+js_not_a_data_binding("CATCH_PARAMETER").
+
+// ── export_var(Module, ExportedName, Variable) — the surface, by variable ───
+export_var(mod, n, v) :- export_decl("client", n, _, _, _, "VARIABLE", v, _, mod, _), n != "", js_module_var(v).
+export_var(mod, n, v) :- export_decl("client", en, _, "EXPORT_ALL", _, _, _, _, mod, x), export_all_is_spread(en),
+ export_reexport(_, _, imp, x), import_module(imp, src),
+ export_var(src, n, v), n != "default".
+export_var(mod, en, v) :- export_decl("client", en, ln, form, _, _, _, _, mod, x), form != "EXPORT_ALL",
+ export_reexport(_, _, imp, x), import_module(imp, src), ln != "",
+ export_var(src, ln, v).
+// ── import_var(ImportHash, Variable) — what a named import binding stands for ─
+import_var(imp, v) :- import_decl("client", _, _, bf, n, _, _, _, imp), import_binding_is_named(bf), n != "",
+ !contains(".", n), import_module(imp, mod), export_var(mod, n, v).
+
+// ── the site ────────────────────────────────────────────────────────────────
+js_var_read_target(e, v) :- expr_kind("client", "IDENTIFIER", _, e), expr_binding("client", v, e), js_module_var(v).
+js_var_read_target(e, v) :- expr_kind("client", "IDENTIFIER", _, e), expr_binding("client", lv, e),
+ var_import("client", imp, lv), import_var(imp, v).
+// A plain `=` never reads the old value; `+=` and `++` read and then write.
+js_var_written(e) :- js_var_read_target(e, _), expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, e).
+js_var_written(e) :- js_var_read_target(e, _), expr_kind(_, "UNARY", _, u), expr_operator(_, op, u), update_operator(op),
+ expr_child(_, u, _, _, e).
+js_var_write_only(e) :- js_var_read_target(e, _), expr_kind(_, "ASSIGNMENT", _, a), expr_operator(_, "=", a),
+ expr_child(_, a, "ASSIGNMENT_TARGET", _, e).
+js_var_access(e, "write") :- js_var_written(e), js_var_write_only(e).
+js_var_access(e, "readwrite") :- js_var_written(e), !js_var_write_only(e).
+js_var_access(e, "read") :- js_var_read_target(e, _), !js_var_written(e).
+// the caller: the callable the expression is written in, else the module initializer
+js_var_read_from(e, m) :- js_var_read_target(e, _), expr_owner("client", m, _, e), m != "".
+js_var_read_from(e, init) :- js_var_read_target(e, _), expr_owner("client", "", mod, e), module_init(_, init, mod).
+
+field_access(e, caller, v, "client", "known_edge", acc) :- js_var_read_target(e, v),
+ js_var_read_from(e, caller), js_var_access(e, acc).
diff --git a/graph/javascript/engine/call-edge-generation/jsx.dl b/graph/javascript/engine/call-edge-generation/jsx.dl
index f3c31375..67a6c893 100644
--- a/graph/javascript/engine/call-edge-generation/jsx.dl
+++ b/graph/javascript/engine/call-edge-generation/jsx.dl
@@ -26,7 +26,10 @@
// A tag bound to a component WRAPPER (`memo(C)`, `forwardRef(fn)`, `observer(C)`,
// `withRouter(C)`) or a LOADER (`lazy(() => import("./x"))`) renders what the wrapper
// was handed or what the loader settles to — see "wrapped and lazy components" below.
-// Not modelled: `connect(…)(C)` and other curried wrappers.
+// Redux's curried `connect(…)(C)` is such a wrapper, and it also hands C the props its
+// map functions return; React context hands `useContext(Ctx)` the provider's `value`
+// — see "props that come from outside the element" below. Not modelled: other curried
+// wrappers.
// ============================================================================
// ── the element and what its tag holds ──────────────────────────────────────
@@ -59,12 +62,32 @@ jsx_loader_name("lazy").
jsx_loader_name("dynamic").
jsx_loader_name("loadable").
jsx_component_loader(w) :- expr_kind(_, "CALL", _, w), call_site("client", _, n, _, _, _, w, _, _), jsx_loader_name(n).
+// A Vue DEFINER — `defineComponent({ setup() {…} })` (Nuxt: `defineNuxtComponent`): the
+// tag invokes the options object's `setup` with its props, or its `render` when there is
+// no setup; a render function setup returns is reached from setup's body. The function
+// form, `defineComponent((props) => () => …)`, is argument 0 itself, as for memo. Named by
+// the definer, so the same object handed to any other call stays out.
+jsx_vue_definer_name("defineComponent").
+jsx_vue_definer_name("defineNuxtComponent").
+jsx_vue_definer(w) :- expr_kind(_, "CALL", _, w), call_site("client", _, n, _, _, _, w, _, _), jsx_vue_definer_name(n).
+// Redux `connect(mapState, mapDispatch)(C)`: the OUTER call is the wrapper, its argument
+// 0 the component. Keyed on the callee being a call of `connect`, so a plain
+// `connect(db)` or a `connect(…)` whose result is not called again stays out.
+jsx_connect_call(w, inner) :- expr_kind(_, "CALL", _, w), expr_child(_, w, "CALLEE", _, inner),
+ expr_kind(_, "CALL", _, inner), call_site("client", _, "connect", _, _, _, inner, _, _).
+jsx_component_wrapper(w) :- jsx_connect_call(w, _).
+// React `createContext(…)`: not a wrapper, but it reaches `` and
+// `useContext(Ctx)` the ways a component reaches a tag, so jsx_holds follows it too.
+// No jsx_call_renders rule reads it: holding a context renders nothing.
+jsx_context_create(c) :- expr_kind(_, "CALL", _, c), call_site("client", _, "createContext", _, _, _, c, _, _).
// jsx_holds(Expr, Call) — the wrapper or loader call an expression holds, followed the
// ways a component reaches a tag: the call itself, a const initialised with it, a named
// or default import of an export that holds it, and a re-export of one.
jsx_holds(w, w) :- jsx_component_wrapper(w).
jsx_holds(w, w) :- jsx_component_loader(w).
+jsx_holds(w, w) :- jsx_vue_definer(w).
+jsx_holds(c, c) :- jsx_context_create(c).
jsx_holds(e, w) :- expr_binding(_, v, e), jsx_var_holds(v, w).
jsx_var_holds(v, w) :- var_init(_, _, e, v), !var_binding_form(_, "OBJECT_PATTERN", v), !var_binding_form(_, "ARRAY_PATTERN", v),
jsx_holds(e, w).
@@ -97,6 +120,39 @@ jsx_call_renders(w, k, i) :- jsx_loader_settles(w, "obj", o), prop_value("obj",
jsx_call_renders(w, k, i) :- jsx_loader_settles(w, k, i), jsx_component_kind(k).
jsx_component_kind("func").
jsx_component_kind("ctor").
+// A Vue definer: the options object's `setup`, else its `render` — one target, not both.
+// "Has a setup" is read off the literal's own keys, not the value layer, which this
+// relation feeds.
+jsx_call_renders(w, "func", m) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_value(a, "obj", o),
+ prop_value("obj", o, "setup", "func", m).
+jsx_call_renders(w, "func", m) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_value(a, "obj", o),
+ !jsx_vue_has_setup(o), prop_value("obj", o, "render", "func", m).
+jsx_call_renders(w, "func", f) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_value(a, "func", f).
+jsx_vue_has_setup(o) :- expr_kind(_, "OBJECT_LITERAL", _, o), expr_child(_, o, "PROPERTY_KEY", _, key), expr_name(_, "setup", key).
+jsx_vue_has_setup(o) :- literal_owns_method(o, m), method_decl(_, "setup", _, _, _, _, _, m).
+
+// ── Vue single-file components ──────────────────────────────────────────────
+// A `.vue` template tag, ``, reaches this file as a JSX_ELEMENT (the parser
+// writes it into the component's virtual script), so everything above applies to it.
+// A definer call renders when its argument is a function or names setup/render — read
+// off the literal's own keys, as jsx_vue_has_setup is.
+jsx_vue_option(a, n) :- literal_owns_method(a, m), method_decl(_, n, _, _, _, _, _, m).
+jsx_vue_option(a, n) :- expr_child(_, a, "PROPERTY_KEY", _, key), expr_name(_, n, key).
+jsx_vue_definer_renders(w) :- jsx_vue_definer(w), call_arg(w, 0, a), expr_kind(_, "FUNCTION_EXPRESSION", _, a).
+jsx_vue_definer_renders(w) :- jsx_vue_definer(w), call_arg(w, 0, a), jsx_vue_option(a, "setup").
+jsx_vue_definer_renders(w) :- jsx_vue_definer(w), call_arg(w, 0, a), jsx_vue_option(a, "render").
+
+// A single-file component, `import Child from "./Child.vue"`: its script is the
+// module's top-level code — a `
+{{ aliasedLocal() }}
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/components/Child.vue b/graph/test/javascript/cases/72-vue-component-tag/src/components/Child.vue
new file mode 100644
index 00000000..4649dc06
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/components/Child.vue
@@ -0,0 +1,4 @@
+
+{{ childLocal() }}
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/components/Defined.vue b/graph/test/javascript/cases/72-vue-component-tag/src/components/Defined.vue
new file mode 100644
index 00000000..64fda779
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/components/Defined.vue
@@ -0,0 +1,8 @@
+
+defined
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/components/Legacy.vue b/graph/test/javascript/cases/72-vue-component-tag/src/components/Legacy.vue
new file mode 100644
index 00000000..0b8279d6
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/components/Legacy.vue
@@ -0,0 +1,6 @@
+
+
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/components/MyWidget.vue b/graph/test/javascript/cases/72-vue-component-tag/src/components/MyWidget.vue
new file mode 100644
index 00000000..01e94047
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/components/MyWidget.vue
@@ -0,0 +1,5 @@
+
+{{ widgetLabel() }}
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/components/badge.js b/graph/test/javascript/cases/72-vue-component-tag/src/components/badge.js
new file mode 100644
index 00000000..91986299
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/components/badge.js
@@ -0,0 +1 @@
+export function Badge() { return "b"; }
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/components/store.js b/graph/test/javascript/cases/72-vue-component-tag/src/components/store.js
new file mode 100644
index 00000000..7e5362e8
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/components/store.js
@@ -0,0 +1 @@
+export default { items: [] };
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/views/Parent.vue b/graph/test/javascript/cases/72-vue-component-tag/src/views/Parent.vue
new file mode 100644
index 00000000..16cdfe43
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/views/Parent.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+ {{ fmt(Store) }}
+
+
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/views/Shell.vue b/graph/test/javascript/cases/72-vue-component-tag/src/views/Shell.vue
new file mode 100644
index 00000000..d9c302fc
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/views/Shell.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/graph/test/javascript/cases/72-vue-component-tag/src/vite.config.js b/graph/test/javascript/cases/72-vue-component-tag/src/vite.config.js
new file mode 100644
index 00000000..798b97d7
--- /dev/null
+++ b/graph/test/javascript/cases/72-vue-component-tag/src/vite.config.js
@@ -0,0 +1,4 @@
+import { fileURLToPath, URL } from "node:url";
+export default {
+ resolve: { alias: { "@": fileURLToPath(new URL("./components", import.meta.url)) } },
+};
diff --git a/graph/test/javascript/cases/73-bound-function-identity/src/bound.js b/graph/test/javascript/cases/73-bound-function-identity/src/bound.js
new file mode 100644
index 00000000..40374a11
--- /dev/null
+++ b/graph/test/javascript/cases/73-bound-function-identity/src/bound.js
@@ -0,0 +1,50 @@
+'use strict';
+const { promisify } = require('util');
+
+class Repo {
+ append(x) { return x; }
+ find(k) { return k; }
+}
+class Other {
+ append(x) { return x; }
+}
+
+// (a) a field or variable assigned from x.m.bind(x) is x.m
+class Service {
+ constructor(r) {
+ this.repo = r;
+ this.add = this.repo.append.bind(this.repo);
+ this.lookup = promisify(r.find.bind(r));
+ }
+ go() { return this.add(1); }
+ get(k) { return this.lookup(k); }
+}
+function makeService() { return new Service(new Repo()); }
+const repo = new Repo();
+const bound = repo.append.bind(repo);
+function viaVariable() { return bound(2); }
+const wrapped = promisify(repo.find.bind(repo));
+function viaPromisified() { return wrapped('k'); }
+const util = require('util');
+const viaNamespace = util.promisify(repo.find.bind(repo));
+function viaNamespaceCall() { return viaNamespace('n'); }
+
+// (b) a function bound to an object literal sees its keys through `this`
+function onEvent(e) { return this.repo.append(e); }
+const handler = onEvent.bind({ repo: new Repo() });
+function fire() { return handler(1); }
+const handlers = {
+ created: function onCreated(e) { return this.store.append(e); },
+};
+const onCreatedBound = handlers.created.bind({ store: new Other() });
+
+// controls: none of these changes
+function unbound(e) { return this.repo.append(e); }
+function callsUnbound() { return unbound.call({ repo: new Repo() }, 1); }
+const snapshot = repo.find.bind(null);
+function openBind(fn) { const g = fn.bind(repo); return g(); }
+const own = { promisify(f) { return () => f; } };
+const notUtil = own.promisify(repo.find);
+function viaOwnPromisify() { return notUtil(); }
+
+module.exports = { makeService, viaVariable, viaPromisified, fire, onCreatedBound, callsUnbound, snapshot, openBind, viaNamespaceCall, viaOwnPromisify };
diff --git a/graph/test/javascript/cases/74-orm-model-factory/src/models.js b/graph/test/javascript/cases/74-orm-model-factory/src/models.js
new file mode 100644
index 00000000..c5b18612
--- /dev/null
+++ b/graph/test/javascript/cases/74-orm-model-factory/src/models.js
@@ -0,0 +1,32 @@
+'use strict';
+// A model made by an ORM factory, `connection.model(name, schema)`, is a class the
+// package builds at runtime from the schema: its statics are the functions written
+// onto `schema.statics` (or handed to `schema.static(...)`), and a document it
+// constructs has the schema's methods. The package is not installed, so nothing in
+// the tree declares the model; the schema argument is what carries its members.
+const mongoose = require('mongoose');
+const { Schema } = mongoose;
+
+const docSchema = new Schema({ title: String });
+docSchema.statics.findLive = function () { return this.find({}); };
+docSchema.static('claim', function () { return this.findLive(); });
+docSchema.static({ purge() { return 0; } });
+docSchema.methods.toRecord = function () { return this.title; };
+docSchema.method('touch', function () { return this.toRecord(); });
+
+// The options form, on the package's namespace.
+const jobSchema = new mongoose.Schema({}, { statics: { due() { return 1; } } });
+
+// Control: a schema-like object from a package that is not modelled stays unknown.
+const { Schema: OtherSchema } = require('other-orm');
+const otherSchema = new OtherSchema({});
+otherSchema.statics.findLive = function () { return 2; };
+
+function registerModels(connection) {
+ return {
+ Doc: connection.models.Doc || connection.model('Doc', docSchema),
+ Job: mongoose.model('Job', jobSchema),
+ Other: connection.model('Other', otherSchema),
+ };
+}
+module.exports = { registerModels };
diff --git a/graph/test/javascript/cases/74-orm-model-factory/src/repo.js b/graph/test/javascript/cases/74-orm-model-factory/src/repo.js
new file mode 100644
index 00000000..55f3b77d
--- /dev/null
+++ b/graph/test/javascript/cases/74-orm-model-factory/src/repo.js
@@ -0,0 +1,31 @@
+'use strict';
+const { registerModels } = require('./models');
+
+class DocRepository {
+ constructor(models) { this.Doc = models.Doc; this.Job = models.Job; this.Other = models.Other; }
+ list() { return this.Doc.findLive(); }
+ take() { return this.Doc.claim(); }
+ purge() { return this.Doc.purge(); }
+ due() { return this.Job.due(); }
+ record() { return new this.Doc({}).touch(); }
+ save() { return this.Doc.create({}); } // the package's own member: stays unknown
+ other() { return this.Other.findLive(); } // control: the unmodelled package
+}
+
+// Control: an unrelated repository with same-named methods, reached by nothing above.
+class MemoryRepository {
+ findLive() { return []; }
+ claim() { return null; }
+}
+
+// Control: a project `.model(name, x)` whose second argument is not a schema keeps
+// its own return value.
+const registry = { model(name, def) { return def; } };
+const plain = registry.model('x', { findLive() { return 3; } });
+
+function start(connection) {
+ const repo = new DocRepository(registerModels(connection));
+ return [repo.list(), repo.take(), repo.purge(), repo.due(), repo.record(), repo.save(), repo.other(),
+ plain.findLive(), new MemoryRepository()];
+}
+module.exports = { start };
diff --git a/graph/test/javascript/expected/08-library-dependency.diag b/graph/test/javascript/expected/08-library-dependency.diag
index aaefacd3..38b34560 100644
--- a/graph/test/javascript/expected/08-library-dependency.diag
+++ b/graph/test/javascript/expected/08-library-dependency.diag
@@ -1,6 +1,9 @@
import_cause app.js:2:13 dep not_staged
import_cause app.js:3:17 dep not_staged
import_cause app.js:3:9 dep not_staged
+library_receiver app.js:5:1 c.get dep
+library_receiver app.js:6:1 new Client().request dep
+library_receiver app.js:8:1 dep.helper dep
package_entry probe6-app . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved app.js:4:11 FUNCTION_CALL dep callee_untyped
unresolved app.js:5:1 METHOD_CALL get receiver_untyped
diff --git a/graph/test/javascript/expected/28-uninstalled-dependency.diag b/graph/test/javascript/expected/28-uninstalled-dependency.diag
index 2a015d3b..b3376dc9 100644
--- a/graph/test/javascript/expected/28-uninstalled-dependency.diag
+++ b/graph/test/javascript/expected/28-uninstalled-dependency.diag
@@ -4,6 +4,21 @@ import_cause app.js:6:14 alpha/missing not_staged
import_cause app.js:7:14 never-staged not_staged
import_cause app.mjs:1:10 alpha not_staged
import_cause app.mjs:2:17 alpha not_staged
+import_cause receivers.js:5:17 never-staged not_staged
+import_cause receivers.js:6:14 alpha/missing not_staged
+library_receiver app.js:10:3 alpha.createClient alpha
+library_receiver app.js:10:3 alpha.createClient().run alpha
+library_receiver app.js:13:3 gone.anything alpha/missing
+library_receiver app.js:14:3 nope.thing never-staged
+library_receiver app.mjs:3:1 createClient().run alpha
+library_receiver app.mjs:4:1 m.createClient alpha
+library_receiver app.mjs:4:1 m.createClient().run alpha
+library_receiver receivers.js:13:9 request(app).get never-staged
+library_receiver receivers.js:13:9 request(app).get('/x').expect never-staged
+library_receiver receivers.js:14:21 request(app).get never-staged
+library_receiver receivers.js:16:3 md.get alpha/missing
+library_receiver receivers.js:21:33 request.mocked never-staged
+library_receiver receivers.js:23:11 request.agent never-staged
package_entry case26-app . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved app.js:10:3 METHOD_CALL createClient receiver_untyped
unresolved app.js:10:3 METHOD_CALL run receiver_untyped
@@ -16,3 +31,16 @@ unresolved app.mjs:3:1 FUNCTION_CALL createClient callee_untyped
unresolved app.mjs:3:1 METHOD_CALL run receiver_untyped
unresolved app.mjs:4:1 METHOD_CALL createClient receiver_untyped
unresolved app.mjs:4:1 METHOD_CALL run receiver_untyped
+unresolved receivers.js:10:21 METHOD_CALL get receiver_untyped
+unresolved receivers.js:13:9 FUNCTION_CALL request callee_untyped
+unresolved receivers.js:13:9 METHOD_CALL expect receiver_untyped
+unresolved receivers.js:13:9 METHOD_CALL get receiver_untyped
+unresolved receivers.js:14:21 FUNCTION_CALL request callee_untyped
+unresolved receivers.js:14:21 METHOD_CALL get receiver_untyped
+unresolved receivers.js:15:14 CONSTRUCTOR_CALL Metadata receiver_untyped
+unresolved receivers.js:16:3 METHOD_CALL get receiver_untyped
+unresolved receivers.js:20:32 METHOD_CALL get receiver_untyped
+unresolved receivers.js:21:33 METHOD_CALL get receiver_untyped
+unresolved receivers.js:21:33 METHOD_CALL mocked receiver_untyped
+unresolved receivers.js:23:11 METHOD_CALL agent receiver_untyped
+unresolved receivers.js:25:10 METHOD_CALL get member_absent
diff --git a/graph/test/javascript/expected/28-uninstalled-dependency.edges b/graph/test/javascript/expected/28-uninstalled-dependency.edges
index cb072a1a..11563c90 100644
--- a/graph/test/javascript/expected/28-uninstalled-dependency.edges
+++ b/graph/test/javascript/expected/28-uninstalled-dependency.edges
@@ -11,3 +11,17 @@ app.mjs:3:1 FUNCTION_CALL createClient -> ambiguous_unknown -
app.mjs:3:1 METHOD_CALL createClient().run -> ambiguous_unknown -
app.mjs:4:1 METHOD_CALL m.createClient -> ambiguous_unknown -
app.mjs:4:1 METHOD_CALL m.createClient().run -> ambiguous_unknown -
+receivers.js:10:21 METHOD_CALL this.repo.get -> ambiguous_unknown -
+receivers.js:13:9 FUNCTION_CALL request -> ambiguous_unknown -
+receivers.js:13:9 METHOD_CALL request(app).get -> ambiguous_unknown -
+receivers.js:13:9 METHOD_CALL request(app).get('/x').expect -> ambiguous_unknown -
+receivers.js:14:21 FUNCTION_CALL request -> ambiguous_unknown -
+receivers.js:14:21 METHOD_CALL request(app).get -> ambiguous_unknown -
+receivers.js:15:14 CONSTRUCTOR_CALL wire.Metadata -> ambiguous_unknown -
+receivers.js:16:3 METHOD_CALL md.get -> ambiguous_unknown -
+receivers.js:20:32 METHOD_CALL obj.get -> ambiguous_unknown -
+receivers.js:21:33 METHOD_CALL request.mocked -> ambiguous_unknown -
+receivers.js:21:33 METHOD_CALL request.mocked(new Repo()).get -> ambiguous_unknown -
+receivers.js:21:48 CONSTRUCTOR_CALL Repo -> implicit_constructor -
+receivers.js:23:11 METHOD_CALL request.agent -> ambiguous_unknown -
+receivers.js:25:10 METHOD_CALL c.get -> ambiguous_unknown -
diff --git a/graph/test/javascript/expected/28-uninstalled-dependency.lib.diag b/graph/test/javascript/expected/28-uninstalled-dependency.lib.diag
index 1e4b56db..cb5c2a22 100644
--- a/graph/test/javascript/expected/28-uninstalled-dependency.lib.diag
+++ b/graph/test/javascript/expected/28-uninstalled-dependency.lib.diag
@@ -1,5 +1,15 @@
import_cause app.js:6:14 alpha/missing staged_unreached
import_cause app.js:7:14 never-staged not_staged
+import_cause receivers.js:5:17 never-staged not_staged
+import_cause receivers.js:6:14 alpha/missing staged_unreached
+library_receiver app.js:13:3 gone.anything alpha/missing
+library_receiver app.js:14:3 nope.thing never-staged
+library_receiver receivers.js:13:9 request(app).get never-staged
+library_receiver receivers.js:13:9 request(app).get('/x').expect never-staged
+library_receiver receivers.js:14:21 request(app).get never-staged
+library_receiver receivers.js:16:3 md.get alpha/missing
+library_receiver receivers.js:21:33 request.mocked never-staged
+library_receiver receivers.js:23:11 request.agent never-staged
package_entry alpha . [] MAIN lib/index.js RESOLVED -> lib:lib/index.js
package_entry alpha . [import] EXPORTS lib/index.mjs RESOLVED -> lib:lib/index.mjs
package_entry alpha . [require] EXPORTS lib/index.js RESOLVED -> lib:lib/index.js
@@ -9,3 +19,16 @@ package_entry case26-app . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved app.js:13:3 METHOD_CALL anything receiver_untyped
unresolved app.js:14:3 METHOD_CALL thing receiver_untyped
unresolved app.mjs:2:17 DYNAMIC_IMPORT_CALL dynamic
+unresolved receivers.js:10:21 METHOD_CALL get receiver_untyped
+unresolved receivers.js:13:9 FUNCTION_CALL request callee_untyped
+unresolved receivers.js:13:9 METHOD_CALL expect receiver_untyped
+unresolved receivers.js:13:9 METHOD_CALL get receiver_untyped
+unresolved receivers.js:14:21 FUNCTION_CALL request callee_untyped
+unresolved receivers.js:14:21 METHOD_CALL get receiver_untyped
+unresolved receivers.js:15:14 CONSTRUCTOR_CALL Metadata receiver_untyped
+unresolved receivers.js:16:3 METHOD_CALL get receiver_untyped
+unresolved receivers.js:20:32 METHOD_CALL get receiver_untyped
+unresolved receivers.js:21:33 METHOD_CALL get receiver_untyped
+unresolved receivers.js:21:33 METHOD_CALL mocked receiver_untyped
+unresolved receivers.js:23:11 METHOD_CALL agent receiver_untyped
+unresolved receivers.js:25:10 METHOD_CALL get member_absent
diff --git a/graph/test/javascript/expected/28-uninstalled-dependency.lib.edges b/graph/test/javascript/expected/28-uninstalled-dependency.lib.edges
index 6dfe1d1b..952c1e2c 100644
--- a/graph/test/javascript/expected/28-uninstalled-dependency.lib.edges
+++ b/graph/test/javascript/expected/28-uninstalled-dependency.lib.edges
@@ -11,3 +11,17 @@ app.mjs:3:1 FUNCTION_CALL createClient -> boundary_lib lib:lib/index.mjs:2:1
app.mjs:3:1 METHOD_CALL createClient().run -> boundary_lib lib:lib/index.mjs:1:21 run
app.mjs:4:1 METHOD_CALL m.createClient -> boundary_lib lib:lib/index.mjs:2:1 createClient
app.mjs:4:1 METHOD_CALL m.createClient().run -> boundary_lib lib:lib/index.mjs:1:21 run
+receivers.js:10:21 METHOD_CALL this.repo.get -> ambiguous_unknown -
+receivers.js:13:9 FUNCTION_CALL request -> ambiguous_unknown -
+receivers.js:13:9 METHOD_CALL request(app).get -> ambiguous_unknown -
+receivers.js:13:9 METHOD_CALL request(app).get('/x').expect -> ambiguous_unknown -
+receivers.js:14:21 FUNCTION_CALL request -> ambiguous_unknown -
+receivers.js:14:21 METHOD_CALL request(app).get -> ambiguous_unknown -
+receivers.js:15:14 CONSTRUCTOR_CALL wire.Metadata -> ambiguous_unknown -
+receivers.js:16:3 METHOD_CALL md.get -> ambiguous_unknown -
+receivers.js:20:32 METHOD_CALL obj.get -> ambiguous_unknown -
+receivers.js:21:33 METHOD_CALL request.mocked -> ambiguous_unknown -
+receivers.js:21:33 METHOD_CALL request.mocked(new Repo()).get -> ambiguous_unknown -
+receivers.js:21:48 CONSTRUCTOR_CALL Repo -> implicit_constructor -
+receivers.js:23:11 METHOD_CALL request.agent -> ambiguous_unknown -
+receivers.js:25:10 METHOD_CALL c.get -> ambiguous_unknown -
diff --git a/graph/test/javascript/expected/28-uninstalled-dependency.oracle b/graph/test/javascript/expected/28-uninstalled-dependency.oracle
index 6d77f120..d56dda00 100644
--- a/graph/test/javascript/expected/28-uninstalled-dependency.oracle
+++ b/graph/test/javascript/expected/28-uninstalled-dependency.oracle
@@ -1,3 +1,4 @@
app.js:11:3 CONSTRUCTOR_CALL Mine SYNTHESIZED_OK
app.js:16:1 FUNCTION_CALL main EXACT app.js:9:1
+receivers.js:21:48 CONSTRUCTOR_CALL Repo SYNTHESIZED_OK
# defects: 0
diff --git a/graph/test/javascript/expected/30-subpath-pattern-export.diag b/graph/test/javascript/expected/30-subpath-pattern-export.diag
index ce53dcce..9a41da01 100644
--- a/graph/test/javascript/expected/30-subpath-pattern-export.diag
+++ b/graph/test/javascript/expected/30-subpath-pattern-export.diag
@@ -6,6 +6,9 @@ import_cause app.js:7:18 kit/internal/secret not_staged
import_cause app.js:8:16 kit/feat/deep/none not_staged
import_cause app.mjs:1:10 kit/mjs/gamma not_staged
import_cause app.mjs:2:10 kit/feat/alpha not_staged
+library_receiver app.js:9:19 createApp().start kit
+library_receiver app.js:9:66 internal.secret kit/internal/secret
+library_receiver app.js:9:85 nested.x kit/feat/deep/none
package_entry case30-app . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved app.js:9:19 FUNCTION_CALL createApp callee_untyped
unresolved app.js:9:19 METHOD_CALL start receiver_untyped
diff --git a/graph/test/javascript/expected/30-subpath-pattern-export.lib.diag b/graph/test/javascript/expected/30-subpath-pattern-export.lib.diag
index 3f4e3746..4844004a 100644
--- a/graph/test/javascript/expected/30-subpath-pattern-export.lib.diag
+++ b/graph/test/javascript/expected/30-subpath-pattern-export.lib.diag
@@ -1,5 +1,7 @@
import_cause app.js:7:18 kit/internal/secret staged_unreached
import_cause app.js:8:16 kit/feat/deep/none staged_unreached
+library_receiver app.js:9:66 internal.secret kit/internal/secret
+library_receiver app.js:9:85 nested.x kit/feat/deep/none
package_entry case30-app . [] DEFAULT_INDEX index.js MISSING_FILE -> -
package_entry kit . [default] EXPORTS src/index.js RESOLVED -> lib:src/index.js
package_entry kit . [node.require] EXPORTS src/index.js RESOLVED -> lib:src/index.js
diff --git a/graph/test/javascript/expected/34-options-object-callbacks.diag b/graph/test/javascript/expected/34-options-object-callbacks.diag
index f261bb39..1d54eea4 100644
--- a/graph/test/javascript/expected/34-options-object-callbacks.diag
+++ b/graph/test/javascript/expected/34-options-object-callbacks.diag
@@ -1,6 +1,10 @@
+import_cause main.js:17:16 assert builtin
import_cause main.js:2:14 walker not_staged
+import_cause main.js:37:136 events builtin
import_cause main.js:3:15 https builtin
import_cause main.js:4:9 stream builtin
+library_receiver main.js:43:25 walk.register walker
+library_receiver main.js:43:48 walk.register walker
package_entry options-callbacks . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved main.js:10:28 FUNCTION_CALL walk callee_untyped
unresolved main.js:11:89 FUNCTION_CALL walk callee_untyped
@@ -8,6 +12,29 @@ unresolved main.js:12:86 METHOD_CALL destroy no_target
unresolved main.js:12:86 METHOD_CALL on no_target
unresolved main.js:12:86 METHOD_CALL request no_target
unresolved main.js:13:29 CONSTRUCTOR_CALL Transform no_target
+unresolved main.js:18:15 CONSTRUCTOR_CALL Map no_target
+unresolved main.js:19:14 CONSTRUCTOR_CALL Set no_target
+unresolved main.js:20:65 METHOD_CALL keys no_target
+unresolved main.js:20:95 METHOD_CALL default receiver_untyped
+unresolved main.js:23:31 METHOD_CALL get no_target
+unresolved main.js:24:32 METHOD_CALL has no_target
+unresolved main.js:25:74 METHOD_CALL add no_target
+unresolved main.js:25:89 METHOD_CALL set no_target
+unresolved main.js:27:25 METHOD_CALL equal no_target
+unresolved main.js:27:95 FUNCTION_CALL walk callee_untyped
+unresolved main.js:28:23 FUNCTION_CALL setTimeout no_target
+unresolved main.js:28:50 METHOD_CALL forEach no_target
+unresolved main.js:32:19 CONSTRUCTOR_CALL Map no_target
+unresolved main.js:33:29 METHOD_CALL get no_target
+unresolved main.js:33:61 CONSTRUCTOR_CALL Set no_target
+unresolved main.js:33:72 METHOD_CALL set no_target
+unresolved main.js:33:95 METHOD_CALL add no_target
+unresolved main.js:35:122 METHOD_CALL emit no_target
+unresolved main.js:37:131 CONSTRUCTOR_CALL no_target
+unresolved main.js:41:16 FUNCTION_CALL walk callee_untyped
+unresolved main.js:42:18 FUNCTION_CALL walk callee_untyped
+unresolved main.js:43:25 METHOD_CALL register receiver_untyped
+unresolved main.js:43:48 METHOD_CALL register receiver_untyped
unresolved main.js:8:38 FUNCTION_CALL cb callee_untyped
unresolved main.js:9:42 METHOD_CALL map no_target
value_callee main.js:8:38 cb parameter
diff --git a/graph/test/javascript/expected/34-options-object-callbacks.edges b/graph/test/javascript/expected/34-options-object-callbacks.edges
index 04390e7a..b6c226f6 100644
--- a/graph/test/javascript/expected/34-options-object-callbacks.edges
+++ b/graph/test/javascript/expected/34-options-object-callbacks.edges
@@ -12,12 +12,55 @@ main.js:12:86 METHOD_CALL https.request(o).on('error', () => {}).destroy -> am
main.js:13:29 CONSTRUCTOR_CALL Transform -> ambient_terminal -
main.js:13:29 CONSTRUCTOR_CALL Transform -> callback_registered main.js:8:1 transform
main.js:14:32 FUNCTION_CALL localWalk -> known_edge main.js:9:1 localWalk
-main.js:15:19 FUNCTION_CALL direct -> known_edge main.js:10:1 direct
-main.js:15:29 FUNCTION_CALL viaVar -> known_edge main.js:11:1 viaVar
-main.js:15:39 FUNCTION_CALL viaPlatform -> known_edge main.js:12:1 viaPlatform
-main.js:15:54 FUNCTION_CALL viaCtor -> known_edge main.js:13:1 viaCtor
-main.js:15:65 FUNCTION_CALL viaProject -> known_edge main.js:14:1 viaProject
-main.js:16:1 FUNCTION_CALL main -> known_edge main.js:15:1 main
+main.js:18:15 CONSTRUCTOR_CALL Map -> ambient_terminal -
+main.js:19:14 CONSTRUCTOR_CALL Set -> ambient_terminal -
+main.js:20:65 METHOD_CALL Object.keys -> ambient_terminal -
+main.js:20:95 METHOD_CALL schema[k].default -> ambiguous_unknown -
+main.js:22:13 FUNCTION_CALL defineConfig -> known_edge main.js:20:1 defineConfig
+main.js:23:31 METHOD_CALL cache.get -> ambient_terminal -
+main.js:24:32 METHOD_CALL subs.has -> ambient_terminal -
+main.js:25:74 METHOD_CALL subs.add -> ambient_terminal -
+main.js:25:89 METHOD_CALL cache.set -> ambient_terminal -
+main.js:27:25 METHOD_CALL assert.equal -> ambient_terminal -
+main.js:27:60 FUNCTION_CALL lookup -> known_edge main.js:23:1 lookup
+main.js:27:74 FUNCTION_CALL known -> known_edge main.js:24:1 known
+main.js:27:95 FUNCTION_CALL walk -> ambiguous_unknown -
+main.js:28:23 FUNCTION_CALL setTimeout -> ambient_terminal -
+main.js:28:23 FUNCTION_CALL setTimeout -> callback_registered main.js:28:34
+main.js:28:40 FUNCTION_CALL keep -> known_edge main.js:5:1 keep
+main.js:28:50 METHOD_CALL [1].forEach -> ambient_terminal -
+main.js:28:50 METHOD_CALL [1].forEach -> callback_registered main.js:6:1 visit
+main.js:32:19 CONSTRUCTOR_CALL Map -> ambient_terminal -
+main.js:33:29 METHOD_CALL byPattern.get -> ambient_terminal -
+main.js:33:61 CONSTRUCTOR_CALL Set -> ambient_terminal -
+main.js:33:72 METHOD_CALL byPattern.set -> ambient_terminal -
+main.js:33:95 METHOD_CALL s.add -> ambient_terminal -
+main.js:35:122 METHOD_CALL bus.emit -> ambient_terminal -
+main.js:35:179 FUNCTION_CALL h -> ambient_terminal -
+main.js:35:179 FUNCTION_CALL h -> multi_inferred main.js:26:1 onUpdated
+main.js:35:84 FUNCTION_CALL matchesKey -> known_edge main.js:34:1 matchesKey
+main.js:36:20 FUNCTION_CALL on -> callback_registered main.js:26:1 onUpdated
+main.js:36:20 FUNCTION_CALL on -> known_edge main.js:33:1 on
+main.js:36:40 FUNCTION_CALL deliver -> known_edge main.js:35:1 deliver
+main.js:37:115 FUNCTION_CALL viaTimer -> known_edge main.js:28:1 viaTimer
+main.js:37:127 FUNCTION_CALL bus -> known_edge main.js:36:1 bus
+main.js:37:131 CONSTRUCTOR_CALL (require('events')) -> ambient_terminal -
+main.js:37:19 FUNCTION_CALL direct -> known_edge main.js:10:1 direct
+main.js:37:29 FUNCTION_CALL viaVar -> known_edge main.js:11:1 viaVar
+main.js:37:39 FUNCTION_CALL viaPlatform -> known_edge main.js:12:1 viaPlatform
+main.js:37:54 FUNCTION_CALL viaCtor -> known_edge main.js:13:1 viaCtor
+main.js:37:65 FUNCTION_CALL viaProject -> known_edge main.js:14:1 viaProject
+main.js:37:79 FUNCTION_CALL check -> known_edge main.js:27:1 check
+main.js:37:85 FUNCTION_CALL subscribe -> callback_registered main.js:26:1 onUpdated
+main.js:37:85 FUNCTION_CALL subscribe -> known_edge main.js:25:1 subscribe
+main.js:38:1 FUNCTION_CALL main -> known_edge main.js:37:1 main
+main.js:41:16 FUNCTION_CALL walk -> ambiguous_unknown -
+main.js:41:16 FUNCTION_CALL walk -> callback_registered main.js:41:21
+main.js:41:33 FUNCTION_CALL migrate -> known_edge main.js:40:1 migrate
+main.js:42:18 FUNCTION_CALL walk -> ambiguous_unknown -
+main.js:43:25 METHOD_CALL walk.register -> ambiguous_unknown -
+main.js:43:25 METHOD_CALL walk.register -> callback_registered main.js:41:21
+main.js:43:48 METHOD_CALL walk.register -> ambiguous_unknown -
main.js:8:38 FUNCTION_CALL cb -> ambiguous_unknown -
main.js:9:42 METHOD_CALL items.map -> ambient_terminal -
main.js:9:42 METHOD_CALL items.map -> callback_registered main.js:9:52
diff --git a/graph/test/javascript/expected/34-options-object-callbacks.lib.diag b/graph/test/javascript/expected/34-options-object-callbacks.lib.diag
index 99d2f7ba..e30f6720 100644
--- a/graph/test/javascript/expected/34-options-object-callbacks.lib.diag
+++ b/graph/test/javascript/expected/34-options-object-callbacks.lib.diag
@@ -1,11 +1,35 @@
+import_cause main.js:17:16 assert builtin
+import_cause main.js:37:136 events builtin
import_cause main.js:3:15 https builtin
import_cause main.js:4:9 stream builtin
+library_receiver main.js:43:25 walk.register walker
+library_receiver main.js:43:48 walk.register walker
package_entry options-callbacks . [] DEFAULT_INDEX index.js MISSING_FILE -> -
package_entry walker . [] MAIN index.js RESOLVED -> lib:index.js
unresolved main.js:12:86 METHOD_CALL destroy no_target
unresolved main.js:12:86 METHOD_CALL on no_target
unresolved main.js:12:86 METHOD_CALL request no_target
unresolved main.js:13:29 CONSTRUCTOR_CALL Transform no_target
+unresolved main.js:18:15 CONSTRUCTOR_CALL Map no_target
+unresolved main.js:19:14 CONSTRUCTOR_CALL Set no_target
+unresolved main.js:20:65 METHOD_CALL keys no_target
+unresolved main.js:20:95 METHOD_CALL default receiver_untyped
+unresolved main.js:23:31 METHOD_CALL get no_target
+unresolved main.js:24:32 METHOD_CALL has no_target
+unresolved main.js:25:74 METHOD_CALL add no_target
+unresolved main.js:25:89 METHOD_CALL set no_target
+unresolved main.js:27:25 METHOD_CALL equal no_target
+unresolved main.js:28:23 FUNCTION_CALL setTimeout no_target
+unresolved main.js:28:50 METHOD_CALL forEach no_target
+unresolved main.js:32:19 CONSTRUCTOR_CALL Map no_target
+unresolved main.js:33:29 METHOD_CALL get no_target
+unresolved main.js:33:61 CONSTRUCTOR_CALL Set no_target
+unresolved main.js:33:72 METHOD_CALL set no_target
+unresolved main.js:33:95 METHOD_CALL add no_target
+unresolved main.js:35:122 METHOD_CALL emit no_target
+unresolved main.js:37:131 CONSTRUCTOR_CALL no_target
+unresolved main.js:43:25 METHOD_CALL register member_absent
+unresolved main.js:43:48 METHOD_CALL register member_absent
unresolved main.js:8:38 FUNCTION_CALL cb callee_untyped
unresolved main.js:9:42 METHOD_CALL map no_target
value_callee main.js:8:38 cb parameter
diff --git a/graph/test/javascript/expected/34-options-object-callbacks.lib.edges b/graph/test/javascript/expected/34-options-object-callbacks.lib.edges
index f5582b13..5af7f9c6 100644
--- a/graph/test/javascript/expected/34-options-object-callbacks.lib.edges
+++ b/graph/test/javascript/expected/34-options-object-callbacks.lib.edges
@@ -12,12 +12,55 @@ main.js:12:86 METHOD_CALL https.request(o).on('error', () => {}).destroy -> am
main.js:13:29 CONSTRUCTOR_CALL Transform -> ambient_terminal -
main.js:13:29 CONSTRUCTOR_CALL Transform -> callback_registered main.js:8:1 transform
main.js:14:32 FUNCTION_CALL localWalk -> known_edge main.js:9:1 localWalk
-main.js:15:19 FUNCTION_CALL direct -> known_edge main.js:10:1 direct
-main.js:15:29 FUNCTION_CALL viaVar -> known_edge main.js:11:1 viaVar
-main.js:15:39 FUNCTION_CALL viaPlatform -> known_edge main.js:12:1 viaPlatform
-main.js:15:54 FUNCTION_CALL viaCtor -> known_edge main.js:13:1 viaCtor
-main.js:15:65 FUNCTION_CALL viaProject -> known_edge main.js:14:1 viaProject
-main.js:16:1 FUNCTION_CALL main -> known_edge main.js:15:1 main
+main.js:18:15 CONSTRUCTOR_CALL Map -> ambient_terminal -
+main.js:19:14 CONSTRUCTOR_CALL Set -> ambient_terminal -
+main.js:20:65 METHOD_CALL Object.keys -> ambient_terminal -
+main.js:20:95 METHOD_CALL schema[k].default -> ambiguous_unknown -
+main.js:22:13 FUNCTION_CALL defineConfig -> known_edge main.js:20:1 defineConfig
+main.js:23:31 METHOD_CALL cache.get -> ambient_terminal -
+main.js:24:32 METHOD_CALL subs.has -> ambient_terminal -
+main.js:25:74 METHOD_CALL subs.add -> ambient_terminal -
+main.js:25:89 METHOD_CALL cache.set -> ambient_terminal -
+main.js:27:25 METHOD_CALL assert.equal -> ambient_terminal -
+main.js:27:60 FUNCTION_CALL lookup -> known_edge main.js:23:1 lookup
+main.js:27:74 FUNCTION_CALL known -> known_edge main.js:24:1 known
+main.js:27:95 FUNCTION_CALL walk -> boundary_lib lib:index.js:2:1 walk
+main.js:28:23 FUNCTION_CALL setTimeout -> ambient_terminal -
+main.js:28:23 FUNCTION_CALL setTimeout -> callback_registered main.js:28:34
+main.js:28:40 FUNCTION_CALL keep -> known_edge main.js:5:1 keep
+main.js:28:50 METHOD_CALL [1].forEach -> ambient_terminal -
+main.js:28:50 METHOD_CALL [1].forEach -> callback_registered main.js:6:1 visit
+main.js:32:19 CONSTRUCTOR_CALL Map -> ambient_terminal -
+main.js:33:29 METHOD_CALL byPattern.get -> ambient_terminal -
+main.js:33:61 CONSTRUCTOR_CALL Set -> ambient_terminal -
+main.js:33:72 METHOD_CALL byPattern.set -> ambient_terminal -
+main.js:33:95 METHOD_CALL s.add -> ambient_terminal -
+main.js:35:122 METHOD_CALL bus.emit -> ambient_terminal -
+main.js:35:179 FUNCTION_CALL h -> ambient_terminal -
+main.js:35:179 FUNCTION_CALL h -> multi_inferred main.js:26:1 onUpdated
+main.js:35:84 FUNCTION_CALL matchesKey -> known_edge main.js:34:1 matchesKey
+main.js:36:20 FUNCTION_CALL on -> callback_registered main.js:26:1 onUpdated
+main.js:36:20 FUNCTION_CALL on -> known_edge main.js:33:1 on
+main.js:36:40 FUNCTION_CALL deliver -> known_edge main.js:35:1 deliver
+main.js:37:115 FUNCTION_CALL viaTimer -> known_edge main.js:28:1 viaTimer
+main.js:37:127 FUNCTION_CALL bus -> known_edge main.js:36:1 bus
+main.js:37:131 CONSTRUCTOR_CALL (require('events')) -> ambient_terminal -
+main.js:37:19 FUNCTION_CALL direct -> known_edge main.js:10:1 direct
+main.js:37:29 FUNCTION_CALL viaVar -> known_edge main.js:11:1 viaVar
+main.js:37:39 FUNCTION_CALL viaPlatform -> known_edge main.js:12:1 viaPlatform
+main.js:37:54 FUNCTION_CALL viaCtor -> known_edge main.js:13:1 viaCtor
+main.js:37:65 FUNCTION_CALL viaProject -> known_edge main.js:14:1 viaProject
+main.js:37:79 FUNCTION_CALL check -> known_edge main.js:27:1 check
+main.js:37:85 FUNCTION_CALL subscribe -> callback_registered main.js:26:1 onUpdated
+main.js:37:85 FUNCTION_CALL subscribe -> known_edge main.js:25:1 subscribe
+main.js:38:1 FUNCTION_CALL main -> known_edge main.js:37:1 main
+main.js:41:16 FUNCTION_CALL walk -> boundary_lib lib:index.js:2:1 walk
+main.js:41:16 FUNCTION_CALL walk -> callback_registered main.js:41:21
+main.js:41:33 FUNCTION_CALL migrate -> known_edge main.js:40:1 migrate
+main.js:42:18 FUNCTION_CALL walk -> boundary_lib lib:index.js:2:1 walk
+main.js:43:25 METHOD_CALL walk.register -> ambiguous_unknown -
+main.js:43:25 METHOD_CALL walk.register -> callback_registered main.js:41:21
+main.js:43:48 METHOD_CALL walk.register -> ambiguous_unknown -
main.js:8:38 FUNCTION_CALL cb -> ambiguous_unknown -
main.js:9:42 METHOD_CALL items.map -> ambient_terminal -
main.js:9:42 METHOD_CALL items.map -> callback_registered main.js:9:52
diff --git a/graph/test/javascript/expected/34-options-object-callbacks.oracle b/graph/test/javascript/expected/34-options-object-callbacks.oracle
index be68d152..ef82a93c 100644
--- a/graph/test/javascript/expected/34-options-object-callbacks.oracle
+++ b/graph/test/javascript/expected/34-options-object-callbacks.oracle
@@ -1,8 +1,33 @@
main.js:14:32 FUNCTION_CALL localWalk EXACT main.js:9:1
-main.js:15:19 FUNCTION_CALL direct EXACT main.js:10:1
-main.js:15:29 FUNCTION_CALL viaVar EXACT main.js:11:1
-main.js:15:39 FUNCTION_CALL viaPlatform EXACT main.js:12:1
-main.js:15:54 FUNCTION_CALL viaCtor EXACT main.js:13:1
-main.js:15:65 FUNCTION_CALL viaProject EXACT main.js:14:1
-main.js:16:1 FUNCTION_CALL main EXACT main.js:15:1
+main.js:18:15 CONSTRUCTOR_CALL Map LIB_AMBIENT_OK
+main.js:19:14 CONSTRUCTOR_CALL Set LIB_AMBIENT_OK
+main.js:20:65 METHOD_CALL keys LIB_AMBIENT_OK
+main.js:22:13 FUNCTION_CALL defineConfig EXACT main.js:20:1
+main.js:23:31 METHOD_CALL get LIB_AMBIENT_OK
+main.js:24:32 METHOD_CALL has LIB_AMBIENT_OK
+main.js:25:74 METHOD_CALL add LIB_AMBIENT_OK
+main.js:25:89 METHOD_CALL set LIB_AMBIENT_OK
+main.js:27:60 FUNCTION_CALL lookup EXACT main.js:23:1
+main.js:27:74 FUNCTION_CALL known EXACT main.js:24:1
+main.js:28:23 FUNCTION_CALL setTimeout LIB_AMBIENT_OK
+main.js:28:40 FUNCTION_CALL keep EXACT main.js:5:1
+main.js:28:50 METHOD_CALL forEach LIB_AMBIENT_OK
+main.js:32:19 CONSTRUCTOR_CALL Map LIB_AMBIENT_OK
+main.js:33:29 METHOD_CALL get LIB_AMBIENT_OK
+main.js:33:61 CONSTRUCTOR_CALL Set LIB_AMBIENT_OK
+main.js:33:72 METHOD_CALL set LIB_AMBIENT_OK
+main.js:35:84 FUNCTION_CALL matchesKey EXACT main.js:34:1
+main.js:36:20 FUNCTION_CALL on EXACT main.js:33:1
+main.js:36:40 FUNCTION_CALL deliver EXACT main.js:35:1
+main.js:37:115 FUNCTION_CALL viaTimer EXACT main.js:28:1
+main.js:37:127 FUNCTION_CALL bus EXACT main.js:36:1
+main.js:37:19 FUNCTION_CALL direct EXACT main.js:10:1
+main.js:37:29 FUNCTION_CALL viaVar EXACT main.js:11:1
+main.js:37:39 FUNCTION_CALL viaPlatform EXACT main.js:12:1
+main.js:37:54 FUNCTION_CALL viaCtor EXACT main.js:13:1
+main.js:37:65 FUNCTION_CALL viaProject EXACT main.js:14:1
+main.js:37:79 FUNCTION_CALL check EXACT main.js:27:1
+main.js:37:85 FUNCTION_CALL subscribe EXACT main.js:25:1
+main.js:38:1 FUNCTION_CALL main EXACT main.js:37:1
+main.js:41:33 FUNCTION_CALL migrate EXACT main.js:40:1
# defects: 0
diff --git a/graph/test/javascript/expected/48-umd-library.diag b/graph/test/javascript/expected/48-umd-library.diag
index 7f81443e..dd637fad 100644
--- a/graph/test/javascript/expected/48-umd-library.diag
+++ b/graph/test/javascript/expected/48-umd-library.diag
@@ -1,5 +1,7 @@
import_cause app.js:3:13 umdlib not_staged
import_cause app.js:4:9 umdlib not_staged
+library_receiver app.js:5:1 umd.format umdlib
+library_receiver app.js:7:1 new umd.Formatter().render umdlib
package_entry case48-app . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved app.js:5:1 METHOD_CALL format receiver_untyped
unresolved app.js:6:1 FUNCTION_CALL format callee_untyped
diff --git a/graph/test/javascript/expected/50-enumerated-copy-keys.edges b/graph/test/javascript/expected/50-enumerated-copy-keys.edges
index 3b463f44..6918da3e 100644
--- a/graph/test/javascript/expected/50-enumerated-copy-keys.edges
+++ b/graph/test/javascript/expected/50-enumerated-copy-keys.edges
@@ -12,8 +12,6 @@ main.js:19:31 METHOD_CALL keys(obj2).forEach -> callback_registered main.js:1
main.js:20:12 FUNCTION_CALL extend -> known_edge main.js:19:1 extend
main.js:21:24 METHOD_CALL d4.alpha -> known_edge main.js:7:15 alpha
main.js:25:55 FUNCTION_CALL ownKeys -> ambient_terminal -
-main.js:25:55 FUNCTION_CALL ownKeys -> callback_registered main.js:7:15 alpha
-main.js:25:55 FUNCTION_CALL ownKeys -> callback_registered main.js:7:42 beta
main.js:26:12 FUNCTION_CALL assign -> known_edge main.js:25:1 assign
main.js:27:24 METHOD_CALL d5.beta -> known_edge main.js:7:42 beta
main.js:28:16 METHOD_CALL Object.getOwnPropertyNames -> ambient_terminal -
diff --git a/graph/test/javascript/expected/60-ts-emit-helpers.diag b/graph/test/javascript/expected/60-ts-emit-helpers.diag
index 9cb642a2..f4182214 100644
--- a/graph/test/javascript/expected/60-ts-emit-helpers.diag
+++ b/graph/test/javascript/expected/60-ts-emit-helpers.diag
@@ -1,4 +1,5 @@
import_cause main.js:5:9 tspkg not_staged
+library_receiver main.js:7:26 new Client().send tspkg
package_entry 60-ts-emit-helpers . [] DEFAULT_INDEX index.js MISSING_FILE -> -
unresolved main.js:7:26 CONSTRUCTOR_CALL Client callee_untyped
unresolved main.js:7:26 METHOD_CALL send receiver_untyped
diff --git a/graph/test/javascript/expected/63-jsx-wrapper-lazy.diag b/graph/test/javascript/expected/63-jsx-wrapper-lazy.diag
index ce31a3fe..cc4ccc71 100644
--- a/graph/test/javascript/expected/63-jsx-wrapper-lazy.diag
+++ b/graph/test/javascript/expected/63-jsx-wrapper-lazy.diag
@@ -6,6 +6,7 @@ import_cause app.jsx:3:10 thing-kit not_staged
import_cause defmemo.jsx:1:10 react not_staged
import_cause parts.jsx:1:10 react not_staged
import_cause parts.jsx:1:16 react not_staged
+library_receiver app.jsx:11:16 React.memo react
package_entry @axiomcode/code-graph . [] MAIN dist/reason.js NOT_STAGED -> -
unresolved app.jsx:10:18 FUNCTION_CALL observer callee_untyped
unresolved app.jsx:11:16 METHOD_CALL memo receiver_untyped
diff --git a/graph/test/javascript/expected/64-memoize-wrapper-result.edges b/graph/test/javascript/expected/64-memoize-wrapper-result.edges
index c3d46e25..46a76a11 100644
--- a/graph/test/javascript/expected/64-memoize-wrapper-result.edges
+++ b/graph/test/javascript/expected/64-memoize-wrapper-result.edges
@@ -1,6 +1,8 @@
app.js:11:3 FUNCTION_CALL getAlpha -> known_edge memoize.js:5:10
+app.js:11:3 FUNCTION_CALL getAlpha -> multi_inferred app.js:7:26
app.js:11:3 METHOD_CALL getAlpha().getHooks -> known_edge alpha.js:2:3 getHooks
app.js:12:3 FUNCTION_CALL getBeta -> known_edge memoize.js:5:10
+app.js:12:3 FUNCTION_CALL getBeta -> multi_inferred app.js:8:25
app.js:12:3 METHOD_CALL getBeta().getHooks -> known_edge beta.js:2:3 getHooks
app.js:16:20 FUNCTION_CALL once -> callback_registered app.js:16:25
app.js:16:20 FUNCTION_CALL once -> known_edge once.js:2:1 once
@@ -15,12 +17,16 @@ app.js:19:19 FUNCTION_CALL lazy -> callback_registered app.js:19:24
app.js:19:19 FUNCTION_CALL lazy -> known_edge once.js:14:1 lazy
app.js:19:30 CONSTRUCTOR_CALL AlphaPlugin -> implicit_constructor -
app.js:22:3 FUNCTION_CALL firstAlpha -> known_edge once.js:5:10
+app.js:22:3 FUNCTION_CALL firstAlpha -> multi_inferred app.js:16:25
app.js:22:3 METHOD_CALL firstAlpha().run -> known_edge alpha.js:3:3 run
app.js:23:3 FUNCTION_CALL makeBeta -> known_edge once.js:15:10
+app.js:23:3 FUNCTION_CALL makeBeta -> multi_inferred app.js:17:23
app.js:23:3 METHOD_CALL makeBeta().run -> known_edge beta.js:3:3 run
app.js:24:3 FUNCTION_CALL firstBeta -> known_edge once.js:5:10
+app.js:24:3 FUNCTION_CALL firstBeta -> multi_inferred app.js:18:24
app.js:24:3 METHOD_CALL firstBeta().run -> known_edge beta.js:3:3 run
app.js:25:3 FUNCTION_CALL makeAlpha -> known_edge once.js:15:10
+app.js:25:3 FUNCTION_CALL makeAlpha -> multi_inferred app.js:19:24
app.js:25:3 METHOD_CALL makeAlpha().run -> known_edge alpha.js:3:3 run
app.js:29:18 FUNCTION_CALL constant -> known_edge once.js:18:1 constant
app.js:29:27 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor -
@@ -57,10 +63,14 @@ declared.js:27:16 FUNCTION_CALL pickShared -> callback_registered declared.js
declared.js:27:16 FUNCTION_CALL pickShared -> known_edge declared.js:20:1 pickShared
declared.js:27:33 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor -
declared.js:30:3 FUNCTION_CALL declAlpha -> known_edge declared.js:9:3 inner
+declared.js:30:3 FUNCTION_CALL declAlpha -> multi_inferred declared.js:22:28
declared.js:30:3 METHOD_CALL declAlpha().run -> known_edge alpha.js:3:3 run
declared.js:31:3 FUNCTION_CALL declBeta -> known_edge declared.js:9:3 inner
+declared.js:31:3 FUNCTION_CALL declBeta -> multi_inferred declared.js:23:27
declared.js:31:3 METHOD_CALL declBeta().run -> known_edge beta.js:3:3 run
declared.js:32:3 FUNCTION_CALL declOfGetter -> known_edge declared.js:9:3 inner
+declared.js:32:3 FUNCTION_CALL declOfGetter -> multi_inferred declared.js:24:26
+declared.js:32:3 FUNCTION_CALL declOfGetter -> multi_inferred memoize.js:5:10
declared.js:32:3 METHOD_CALL declOfGetter().run -> known_edge alpha.js:3:3 run
declared.js:33:3 FUNCTION_CALL tappedDecl -> known_edge declared.js:15:3 inner
declared.js:33:3 METHOD_CALL tappedDecl().markOnly -> known_edge declared.js:13:14 markOnly
@@ -202,24 +212,33 @@ shapes.js:45:27 FUNCTION_CALL memoize -> callback_registered shapes.js:45:35
shapes.js:45:27 FUNCTION_CALL memoize -> known_edge memoize.js:2:17
shapes.js:45:41 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor -
shapes.js:48:3 FUNCTION_CALL getFb -> known_edge shapes.js:15:10
+shapes.js:48:3 FUNCTION_CALL getFb -> multi_inferred shapes.js:36:28
shapes.js:48:3 METHOD_CALL getFb().run -> known_edge alpha.js:3:3 run
shapes.js:49:3 FUNCTION_CALL getFb -> known_edge shapes.js:15:10
+shapes.js:49:3 FUNCTION_CALL getFb -> multi_inferred shapes.js:36:28
shapes.js:49:3 METHOD_CALL getFb().fallbackOnly -> known_edge shapes.js:8:18 fallbackOnly
shapes.js:50:3 FUNCTION_CALL getOr -> known_edge shapes.js:22:10
+shapes.js:50:3 FUNCTION_CALL getOr -> multi_inferred shapes.js:38:22
shapes.js:50:3 METHOD_CALL getOr().run -> ambient_terminal -
shapes.js:50:3 METHOD_CALL getOr().run -> multi_inferred beta.js:3:3 run
shapes.js:51:3 FUNCTION_CALL getOr -> known_edge shapes.js:22:10
+shapes.js:51:3 FUNCTION_CALL getOr -> multi_inferred shapes.js:38:22
shapes.js:51:3 METHOD_CALL getOr().mockOnly -> ambient_terminal -
shapes.js:51:3 METHOD_CALL getOr().mockOnly -> multi_inferred shapes.js:9:14 mockOnly
shapes.js:52:3 FUNCTION_CALL getD -> known_edge shapes.js:29:10
+shapes.js:52:3 FUNCTION_CALL getD -> multi_inferred shapes.js:39:26
shapes.js:52:3 METHOD_CALL getD().run -> known_edge alpha.js:3:3 run
shapes.js:53:3 FUNCTION_CALL getD -> known_edge shapes.js:29:10
+shapes.js:53:3 FUNCTION_CALL getD -> multi_inferred shapes.js:39:26
shapes.js:53:3 METHOD_CALL getD().defaultsOnly -> known_edge shapes.js:25:20 defaultsOnly
shapes.js:54:3 FUNCTION_CALL getDefault -> known_edge shapes.js:33:44
shapes.js:54:3 METHOD_CALL getDefault().defaultOnly -> known_edge shapes.js:10:19 defaultOnly
shapes.js:55:3 FUNCTION_CALL getSpread -> known_edge shapes.js:33:44
shapes.js:55:3 METHOD_CALL getSpread().run -> ambiguous_unknown -
shapes.js:56:3 FUNCTION_CALL warm -> known_edge shapes.js:34:32
+shapes.js:56:3 FUNCTION_CALL warm -> multi_inferred shapes.js:43:20
shapes.js:56:3 METHOD_CALL warm().run -> known_edge alpha.js:3:3 run
shapes.js:57:3 FUNCTION_CALL getNested -> known_edge memoize.js:5:10
+shapes.js:57:3 FUNCTION_CALL getNested -> multi_inferred memoize.js:5:10
+shapes.js:57:3 FUNCTION_CALL getNested -> multi_inferred shapes.js:45:35
shapes.js:57:3 METHOD_CALL getNested().run -> known_edge beta.js:3:3 run
diff --git a/graph/test/javascript/expected/65-wrapper-param-reassigned.edges b/graph/test/javascript/expected/65-wrapper-param-reassigned.edges
index d56c695d..8373c4e3 100644
--- a/graph/test/javascript/expected/65-wrapper-param-reassigned.edges
+++ b/graph/test/javascript/expected/65-wrapper-param-reassigned.edges
@@ -2,51 +2,66 @@ app.js:10:23 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:10:3 FUNCTION_CALL lazyOrDefault -> callback_registered app.js:10:17
app.js:10:3 FUNCTION_CALL lazyOrDefault -> known_edge wrap.js:17:1 lazyOrDefault
app.js:10:3 FUNCTION_CALL lazyOrDefault(() => new A()) -> known_edge wrap.js:19:10
+app.js:10:3 FUNCTION_CALL lazyOrDefault(() => new A()) -> multi_inferred app.js:10:17
+app.js:10:3 FUNCTION_CALL lazyOrDefault(() => new A()) -> multi_inferred wrap.js:18:15
app.js:10:3 METHOD_CALL lazyOrDefault(() => new A())().aOnly -> known_edge wrap.js:2:11 aOnly
app.js:11:3 FUNCTION_CALL lazyNullish -> known_edge wrap.js:22:1 lazyNullish
app.js:11:3 FUNCTION_CALL lazyNullish() -> known_edge wrap.js:24:10
+app.js:11:3 FUNCTION_CALL lazyNullish() -> multi_inferred wrap.js:23:10
app.js:11:3 METHOD_CALL lazyNullish()().dfltOnly -> known_edge wrap.js:4:14 dfltOnly
app.js:15:15 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:15:3 FUNCTION_CALL plain -> callback_registered app.js:15:9
app.js:15:3 FUNCTION_CALL plain -> known_edge wrap.js:27:1 plain
app.js:15:3 FUNCTION_CALL plain(() => new A()) -> known_edge wrap.js:28:10
+app.js:15:3 FUNCTION_CALL plain(() => new A()) -> multi_inferred app.js:15:9
app.js:15:3 METHOD_CALL plain(() => new A())().aOnly -> known_edge wrap.js:2:11 aOnly
app.js:16:15 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:16:3 FUNCTION_CALL plain -> callback_registered app.js:16:9
app.js:16:3 FUNCTION_CALL plain -> known_edge wrap.js:27:1 plain
app.js:16:3 FUNCTION_CALL plain(() => new A()) -> known_edge wrap.js:28:10
+app.js:16:3 FUNCTION_CALL plain(() => new A()) -> multi_inferred app.js:16:9
app.js:16:3 METHOD_CALL plain(() => new A())().replacedOnly -> ambiguous_unknown -
app.js:17:22 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:17:3 FUNCTION_CALL otherWritten -> callback_registered app.js:17:16
app.js:17:3 FUNCTION_CALL otherWritten -> known_edge wrap.js:31:1 otherWritten
app.js:17:3 FUNCTION_CALL otherWritten(() => new A(), null) -> known_edge wrap.js:33:10
+app.js:17:3 FUNCTION_CALL otherWritten(() => new A(), null) -> multi_inferred app.js:17:16
app.js:17:3 METHOD_CALL otherWritten(() => new A(), null)().aOnly -> known_edge wrap.js:2:11 aOnly
app.js:18:22 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:18:3 FUNCTION_CALL otherWritten -> callback_registered app.js:18:16
app.js:18:3 FUNCTION_CALL otherWritten -> known_edge wrap.js:31:1 otherWritten
app.js:18:3 FUNCTION_CALL otherWritten(() => new A(), null) -> known_edge wrap.js:33:10
+app.js:18:3 FUNCTION_CALL otherWritten(() => new A(), null) -> multi_inferred app.js:18:16
app.js:18:3 METHOD_CALL otherWritten(() => new A(), null)().replacedOnly -> ambiguous_unknown -
app.js:4:14 FUNCTION_CALL swapped -> callback_registered app.js:4:22
app.js:4:14 FUNCTION_CALL swapped -> known_edge wrap.js:7:1 swapped
app.js:4:28 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:5:3 FUNCTION_CALL sw -> known_edge wrap.js:9:10
+app.js:5:3 FUNCTION_CALL sw -> multi_inferred wrap.js:8:8
app.js:5:3 METHOD_CALL sw().replacedOnly -> known_edge wrap.js:3:18 replacedOnly
app.js:6:3 FUNCTION_CALL sw -> known_edge wrap.js:9:10
+app.js:6:3 FUNCTION_CALL sw -> multi_inferred wrap.js:8:8
app.js:6:3 METHOD_CALL sw().aOnly -> ambiguous_unknown -
app.js:7:3 FUNCTION_CALL lazyGuard -> known_edge wrap.js:12:1 lazyGuard
app.js:7:3 FUNCTION_CALL lazyGuard(undefined) -> known_edge wrap.js:14:10
+app.js:7:3 FUNCTION_CALL lazyGuard(undefined) -> multi_inferred wrap.js:13:38
app.js:7:3 METHOD_CALL lazyGuard(undefined)().replacedOnly -> known_edge wrap.js:3:18 replacedOnly
app.js:8:19 CONSTRUCTOR_CALL A -> implicit_constructor -
app.js:8:3 FUNCTION_CALL lazyGuard -> callback_registered app.js:8:13
app.js:8:3 FUNCTION_CALL lazyGuard -> known_edge wrap.js:12:1 lazyGuard
app.js:8:3 FUNCTION_CALL lazyGuard(() => new A()) -> known_edge wrap.js:14:10
+app.js:8:3 FUNCTION_CALL lazyGuard(() => new A()) -> multi_inferred app.js:8:13
+app.js:8:3 FUNCTION_CALL lazyGuard(() => new A()) -> multi_inferred wrap.js:13:38
app.js:8:3 METHOD_CALL lazyGuard(() => new A())().aOnly -> known_edge wrap.js:2:11 aOnly
app.js:9:3 FUNCTION_CALL lazyOrDefault -> known_edge wrap.js:17:1 lazyOrDefault
app.js:9:3 FUNCTION_CALL lazyOrDefault() -> known_edge wrap.js:19:10
+app.js:9:3 FUNCTION_CALL lazyOrDefault() -> multi_inferred wrap.js:18:15
app.js:9:3 METHOD_CALL lazyOrDefault()().dfltOnly -> known_edge wrap.js:4:14 dfltOnly
many.js:11:3 FUNCTION_CALL s21 -> known_edge wrap.js:9:10
+many.js:11:3 FUNCTION_CALL s21 -> multi_inferred wrap.js:8:8
many.js:11:3 METHOD_CALL s21().replacedOnly -> known_edge wrap.js:3:18 replacedOnly
many.js:12:3 FUNCTION_CALL s21 -> known_edge wrap.js:9:10
+many.js:12:3 FUNCTION_CALL s21 -> multi_inferred wrap.js:8:8
many.js:12:3 METHOD_CALL s21().aOnly -> ambiguous_unknown -
many.js:3:13 FUNCTION_CALL swapped -> callback_registered many.js:3:21
many.js:3:13 FUNCTION_CALL swapped -> known_edge wrap.js:7:1 swapped
diff --git a/graph/test/javascript/expected/69-vue-sfc.edges b/graph/test/javascript/expected/69-vue-sfc.edges
index d7efd225..47623a7d 100644
--- a/graph/test/javascript/expected/69-vue-sfc.edges
+++ b/graph/test/javascript/expected/69-vue-sfc.edges
@@ -1,5 +1,5 @@
Comp.vue:10:34 FUNCTION_CALL sfcHelper -> known_edge Comp.vue:3:1 sfcHelper
-Comp.vue:10:6 FUNCTION_CALL Child -> known_edge child.js:1:1 Child
-Comp.vue:11:6 FUNCTION_CALL MyBadge -> known_edge child.js:5:1 MyBadge
+Comp.vue:10:6 JSX_ELEMENT Child(/**/) -> known_edge child.js:1:1 Child
+Comp.vue:11:6 JSX_ELEMENT MyBadge(/*