Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions graph/bundle/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ THE GRAPH. One row per (site, resolved target). A site with N possible targets h
| `new` | csharp | An object creation. The target is the constructed type's constructor, and it is never dispatched. |
| `ctor_delegate` | csharp | `: this(...)` or `: base(...)`. No name is written, so the target is structural. |
| `primary_ctor_base` | csharp | SYNTHESISED. A primary constructor's base invocation, written in the heritage clause: `class D(int a) : B(a)`. There is no call syntax anywhere in the body. FromExpr is the heritage type reference. |
| `implicit_base_ctor` | csharp | SYNTHESISED. The base class's parameterless constructor that a constructor with no `: base(...)` / `: this(...)` runs before its body, or that the implicit constructor of a class declaring none runs at its `new`. FromExpr is the heritage type reference, or the `new` site. |
| `delegate` | csharp | A call through a delegate value: `handler(x)` or `handler.Invoke(x)`. |
| `operator` | csharp | A user-defined operator invoked by operator syntax. |
| `conversion` | csharp | A user-defined conversion. An implicit one has no syntax at the call site. |
Expand Down
1 change: 1 addition & 0 deletions graph/bundle/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ export const VOCAB: readonly VocabSpec[] = [
{ table: 'call_edges', column: 'kind', value: 'new', languages: C, meaning: 'An object creation. The target is the constructed type\'s constructor, and it is never dispatched.' },
{ table: 'call_edges', column: 'kind', value: 'ctor_delegate', languages: C, meaning: '`: this(...)` or `: base(...)`. No name is written, so the target is structural.' },
{ table: 'call_edges', column: 'kind', value: 'primary_ctor_base', languages: C, meaning: 'SYNTHESISED. A primary constructor\'s base invocation, written in the heritage clause: `class D(int a) : B(a)`. There is no call syntax anywhere in the body. FromExpr is the heritage type reference.' },
{ table: 'call_edges', column: 'kind', value: 'implicit_base_ctor', languages: C, meaning: 'SYNTHESISED. The base class\'s parameterless constructor that a constructor with no `: base(...)` / `: this(...)` runs before its body, or that the implicit constructor of a class declaring none runs at its `new`. FromExpr is the heritage type reference, or the `new` site.' },
{ table: 'call_edges', column: 'kind', value: 'delegate', languages: C, meaning: 'A call through a delegate value: `handler(x)` or `handler.Invoke(x)`.' },
{ table: 'call_edges', column: 'kind', value: 'operator', languages: C, meaning: 'A user-defined operator invoked by operator syntax.' },
{ table: 'call_edges', column: 'kind', value: 'conversion', languages: C, meaning: 'A user-defined conversion. An implicit one has no syntax at the call site.' },
Expand Down
46 changes: 46 additions & 0 deletions graph/csharp/engine/call-edge-generation/call_chain.dl
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,52 @@ call_chain_edge(tr, caller, "-", ctor, "lib", "boundary_lib", "primary_ctor_base
method_decl("lib", _, _, _, ctor),
type_ctor("client", gk, caller).

// ── SYNTHESISED EDGES: THE IMPLICIT `base()` ────────────────────────────────
// A constructor that writes neither `: base(..)` nor `: this(..)` runs its base
// class's parameterless constructor before its own body, and a class that declares
// no constructor at all gets one that does the same. Neither is written anywhere, so
// without these edges a base constructor that every derived class runs has no caller.
//
// implicit_base_ctor(TypeGroup, Ctor): the constructor `base()` runs for a type --
// its base class's constructor that takes no argument, or, where the base declares
// no constructor either, the one the base's own implicit constructor runs.
implicit_base_ctor(gk, ctor) :-
type_extends("client", gk, bgk),
type_ctor("client", bgk, ctor), ctor_accepts_argc("client", ctor, "0").
implicit_base_ctor(gk, ctor) :-
type_extends("client", gk, bgk),
!type_ctor("client", bgk, _), type_group("client", _, bgk),
implicit_base_ctor(bgk, ctor).

// A constructor that hands off with `: this(..)` or `: base(..)`, or a primary
// constructor whose heritage passes arguments: the base constructor it runs is the
// one written there, already an edge.
ctor_has_initializer(m) :- call_is_base_ctor("client", e), expr_ultimate_method("client", e, m).
ctor_has_initializer(m) :- call_is_this_ctor("client", e), expr_ultimate_method("client", e, m).
ctor_has_initializer(m) :-
heritage_has_ctor_args("client", t, _), type_group("client", t, gk), type_ctor("client", gk, m).

// The FromExpr is the base class's entry in the heritage clause, as for a primary
// constructor's base invocation: the closest thing to where the call is written.
implicit_base_site(gk, tr) :-
type_extends("client", gk, bgk),
heritage_resolves("client", gk, pos, bgk),
heritage_of_entity("client", gk, pos, _, _, tr), tr != "".

// (1) A declared constructor with no initializer.
call_chain_edge(tr, m, "-", ctor, "client", "known_edge", "implicit_base_ctor") :-
type_ctor("client", gk, m), method_decl("client", _, _, _, m),
!ctor_has_initializer(m),
implicit_base_ctor(gk, ctor),
implicit_base_site(gk, tr).

// (2) `new T()` where T declares no constructor: the implicit one runs T's base's.
// The edge sits on the `new` site itself, beside its known_implicit_ctor row.
call_chain_edge(e, caller, "-", ctor, "client", "known_edge", "implicit_base_ctor") :-
call_ctor_implicit("client", e, gk),
implicit_base_ctor(gk, ctor),
call_from_expr("client", e, caller).

// ── ACCESSOR EDGES: A PROPERTY READ IS A CALL ───────────────────────────────
// `x.Name` invokes get_Name, `a[i]` an indexer accessor, `e += h` an add accessor.
// These have no cs_call_site row -- the parser cannot know whether `x.Name` is a
Expand Down
24 changes: 24 additions & 0 deletions graph/csharp/engine/containment/type-nesting.dl
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,27 @@ module_in_scope(prov, mod, outer) :-

// The GLOBAL namespace ("") is always in scope.
module_in_scope(prov, mod, "") :- module_decl(prov, _, _, _, mod).

// module_ns_chain(Prov, ModuleHash, Namespace) -- the namespaces a name written in this
// file is read from: the file's own namespaces and every dotted prefix of them, as
// text. A prefix that holds no type of its own (`Acme` over `Acme.Shop`) still
// qualifies a name written below it, so this is not ns_encloses, which needs one.
module_ns_chain(prov, mod, ns) :- module_namespace_own(prov, mod, ns), ns != "".
module_ns_chain(prov, mod, p) :- module_namespace_own(prov, mod, inner), ns_prefix(prov, inner, p).

// ── A USING WRITTEN INSIDE A NAMESPACE ─────────────────────────────────────
// `namespace Shop.Tests; using Results;` imports Shop.Results. C# reads the name in
// a using directive from the namespace the directive sits in, innermost outward,
// exactly as it reads any other name there. Taken as written it names a namespace
// that does not exist, and every type the file names through it reads as a library
// type.
//
// The IR does not record whether a using sits inside the namespace or above it.
// The language decides it instead: a using whose name is no namespace at all only
// compiles inside a namespace that has it as a child. So the relative reading is
// taken only when the name as written names nothing, and only for a child that
// exists -- a file whose namespaces have no such child gains nothing.
module_in_scope(prov, mod, full) :-
using_namespace(prov, mod, n), !ns_exists(prov, n),
module_ns_chain(prov, mod, e), full = cat(e, cat(".", n)),
ns_exists(prov, full).
19 changes: 19 additions & 0 deletions graph/csharp/engine/expression-resolution/expr-type.dl
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,25 @@ expr_type(prov, e, gk) :-
expr_type(prov, q, qgk),
member_lookup(prov, qgk, n, "field", f), field_type(prov, f, gk).

// A STATIC member read through its TYPE NAME: `Context.Current.Factory.Create()`.
// The qualifier `Context` names a type, not a value, so it has no expr_type and the
// two clauses above cannot start from it: the getter of `Current` is an edge
// (properties.dl reads the type name), and these give its RESULT a type, so that
// `.Factory` and every call after it resolve. ref_names_type already refuses a name
// that also binds a value, which is the language's member-over-type rule.
expr_type(prov, e, gk) :-
expr_kind(prov, e, "MEMBER_ACCESS"),
expr_qualifier_child(prov, e, q), expr_member_name_child(prov, e, nameExpr),
expr_written_name(prov, nameExpr, n),
ref_names_type(prov, q, qgk),
member_lookup(prov, qgk, n, "property", p), property_type(prov, p, gk).
expr_type(prov, e, gk) :-
expr_kind(prov, e, "MEMBER_ACCESS"),
expr_qualifier_child(prov, e, q), expr_member_name_child(prov, e, nameExpr),
expr_written_name(prov, nameExpr, n),
ref_names_type(prov, q, qgk),
member_lookup(prov, qgk, n, "field", f), field_type(prov, f, gk).

// ── A GENERIC ARGUMENT, SUBSTITUTED FOR THE PARAMETER IT FILLS ──────────────
// `IWrap<Settings> 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,
Expand Down
9 changes: 9 additions & 0 deletions graph/csharp/engine/projections/expressions.dl
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>` 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<T>` 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),
Expand Down
4 changes: 4 additions & 0 deletions graph/csharp/engine/projections/variables.dl
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
19 changes: 19 additions & 0 deletions graph/csharp/engine/resolution/extensions.dl
Original file line number Diff line number Diff line change
Expand Up @@ -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<IServiceCollection> 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<Item>`), which is how every
// other receiver is named; the written text only where no reference was recorded.
Expand Down
7 changes: 4 additions & 3 deletions graph/csharp/engine/resolution/local-binding.dl
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>.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
Expand All @@ -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
Expand Down
24 changes: 22 additions & 2 deletions graph/csharp/engine/resolution/type-resolution.dl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).

Expand Down
8 changes: 7 additions & 1 deletion graph/csharp/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@
.decl call_callee_name(c0:symbol,c1:symbol,c2:symbol)
.decl call_caller_unknown(c0:symbol,c1:symbol)
.decl call_chain_edge(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol,c6:symbol)
.decl call_class_kind(c0: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)
Expand Down Expand Up @@ -265,6 +265,7 @@
.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_expr(c0:symbol,c1:symbol)
Expand Down Expand Up @@ -359,6 +360,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)
Expand Down Expand Up @@ -487,8 +489,10 @@
.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_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)
Expand Down Expand Up @@ -663,6 +667,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)
Expand Down Expand Up @@ -948,6 +953,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2142,6 +2142,13 @@ function describeExpression(
const argumentList = childOfType(node, 'type_argument_list');
shape.typeArgumentCount =
argumentList === undefined ? 0 : namedChildren(argumentList).length;
// The name without its arguments, as an `identifier` row carries it; the
// arity is typeArgumentCount. Without it the receiver of `Cache<int>.Get()`
// has no name, and nothing can say which type it names.
const name = childOfType(node, 'identifier');
if (name !== undefined) {
shape.potentialQualifiedName = normalizeCSharpIdentifier(name.text);
}
break;
}

Expand Down
2 changes: 2 additions & 0 deletions plugins/axiomcode/skills/axiomcode/scripts/ax_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
'new': 'new', 'CONSTRUCTOR_CALL': 'new', 'anon_new': 'new', 'METACLASS_CREATION': 'new',
# one constructor to another
'ctor_delegate': 'ctor', 'SUPER_CALL': 'super',
# C#: a base constructor no syntax names: the implicit `base()` a constructor without an initializer runs
'implicit_base_ctor': 'ctor',
# the callable is named, not called at that line — it runs when whoever took it runs it
'ref': 'method-ref',
# a declaration handed to a decorator, which is what wires most framework handlers up
Expand Down
Loading
Loading