Skip to content
Open
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
3 changes: 2 additions & 1 deletion graph/csharp/engine/expression-resolution/expr-type.dl
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ expr_new_type(prov, e, gk) :-
!expr_new_type_ref(prov, e, _),
call_callee_name(prov, e, n), expr_ultimate_module(prov, e, mod),
call_type_argc(prov, e, ar),
type_name_resolves(prov, mod, n, ar, gk).
type_name_resolves(prov, mod, n, ar, gk),
!expr_callee_name_dropped(prov, e, gk).

// AN INVOCATION has its callee's return type.
expr_type(prov, e, gk) :-
Expand Down
3 changes: 2 additions & 1 deletion graph/csharp/engine/resolution/local-binding.dl
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ ref_names_type(prov, e, gk) :-
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, ar, gk).
type_name_resolves(prov, mod, n, ar, gk),
!expr_name_dropped(prov, e, gk).

// ── IMPLICIT `this` ─────────────────────────────────────────────────────────
// An unqualified instance member reference has an implicit `this` receiver whose
Expand Down
1 change: 1 addition & 0 deletions graph/csharp/engine/resolution/type-hierarchy.dl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ heritage_resolves(prov, gk, pos, bgk) :-
heritage_of_entity(prov, gk, pos, bn, ba, ""),
type_group(prov, t, gk), type_module(prov, t, mod),
type_name_resolves(prov, mod, bn, ba, bgk),
!heritage_name_dropped(prov, t, bn, bgk),
bgk != gk.

// ── THE SPLIT ───────────────────────────────────────────────────────────────
Expand Down
104 changes: 103 additions & 1 deletion graph/csharp/engine/resolution/type-resolution.dl
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,107 @@ 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).

// ── ...FROM THE SITE'S NAMESPACE, NOT THE FILE'S ────────────────────────────
// The candidate above is per FILE: module_ns_chain holds every namespace the file
// declares a type in. A file declaring P.Models, P.Other.Models and a class in P
// therefore offered `Models.Report` written in P both P.Models.Report and
// P.Other.Models.Report -- and P.Other is a child of P, not in scope there.
//
// C# reads the first segment from the namespaces enclosing the SITE, innermost
// outward, and the innermost level that has a match wins: it does not merge
// levels, and a name found at a level hides the same name read in full from the
// global namespace. So per (site namespace, written name), the candidates are
// ranked by the length of the prefix they were found under and only the longest
// survives; a full match is the outermost level and survives only when no
// enclosing namespace matched.
//
// PRUNE-ONLY. The file-level answer is kept as it is and a site drops from it only
// what its own namespace rules out; the site's own answer is always one of the
// file-level ones, so nothing correct is lost. A site whose enclosing type is not
// known drops nothing.
type_name_rel_at(prov, n, ar, gk, p) :-
type_qn_tail(prov, qn, p, n),
type_decl(prov, _, qn, ar, _, t), type_group(prov, t, gk).
type_name_rel_src(prov, n, ar, gk) :- type_name_rel_at(prov, n, ar, gk, _).

// The namespaces a name written in `ns` is read from, with the length of each.
ns_read_level(prov, ns, ns, l) :- ns_exists(prov, ns), l = strlen(ns).
ns_read_level(prov, ns, p, l) :- ns_prefix(prov, ns, p), l = strlen(p).

// A dotted name some site in this file actually writes. type_name_resolves answers
// every tail of every type in every file whose chain holds its prefix, written or
// not, so the scoping below is keyed on what is written. Keyed by FILE as well, so
// the probe into type_name_resolves binds (module, name) and reuses the index its
// consumers already need rather than building one on the name alone.
type_name_written_dotted(prov, mod, n) :-
type_ref_name(prov, tr, n), contains(".", n), type_ref_module(prov, tr, mod).
type_name_written_dotted(prov, mod, n) :-
expr_written_name(prov, e, n), contains(".", n), expr_ultimate_module(prov, e, mod).
type_name_written_dotted(prov, mod, n) :-
call_callee_name(prov, e, n), contains(".", n), call_module(prov, e, mod).
type_name_written_dotted(prov, mod, n) :-
heritage_slot(prov, t, _, n, _, ""), contains(".", n), type_module(prov, t, mod).

type_name_scope_demand(prov, mod, ns, n, ar) :-
type_name_written_dotted(prov, mod, n),
type_name_resolves(prov, mod, n, ar, _),
module_namespace_own(prov, mod, ns).
type_name_scope_level(prov, ns, n, ar, gk, l) :-
type_name_scope_demand(prov, _, ns, n, ar),
ns_read_level(prov, ns, p, l),
type_name_rel_at(prov, n, ar, gk, p).
type_name_scope_best(prov, ns, n, ar, b) :-
type_name_scope_level(prov, ns, n, ar, _, _),
b = max l : { type_name_scope_level(prov, ns, n, ar, _, l) }.
type_name_scope_keep(prov, ns, n, ar, gk) :-
type_name_scope_level(prov, ns, n, ar, gk, b),
type_name_scope_best(prov, ns, n, ar, b).

// type_name_out_of_scope(Prov, Namespace, Name, Arity, GroupKey) -- a file-level
// answer that a site in `ns` does not see: a relative candidate not at the
// innermost matching level (a child or sibling namespace, or a shallower level),
// or a full match hidden by a relative one.
type_name_out_of_scope(prov, ns, n, ar, gk) :-
type_name_scope_demand(prov, mod, ns, n, ar),
type_name_resolves(prov, mod, n, ar, gk),
type_name_rel_src(prov, n, ar, gk),
!type_name_scope_keep(prov, ns, n, ar, gk).
type_name_out_of_scope(prov, ns, n, ar, gk) :-
type_name_scope_demand(prov, mod, ns, n, ar),
type_name_resolves(prov, mod, n, ar, gk),
!type_name_rel_src(prov, n, ar, gk),
type_name_scope_best(prov, ns, n, ar, _).

// The site side, one per consumer below: the written name, the site's enclosing
// type, and that type's namespace. Joined from the (small) out-of-scope set, so only
// sites that write a pruned name are visited.
type_ref_scope_type(prov, tr, t) :- type_ref_owner(prov, tr, o, "TYPE"), t = o.
type_ref_scope_type(prov, tr, t) :- type_ref_owner(prov, tr, o, "METHOD"), method_owner(prov, t, o).
type_ref_scope_type(prov, tr, t) :- type_ref_owner(prov, tr, o, "FIELD"), field_decl(prov, _, t, o).
type_ref_scope_type(prov, tr, t) :- type_ref_owner(prov, tr, o, "PROPERTY"), property_decl(prov, _, t, o).
type_ref_scope_type(prov, tr, t) :- type_ref_owner(prov, tr, o, "EVENT"), event_decl(prov, _, t, o).
type_ref_scope_type(prov, tr, t) :-
type_ref_owner(prov, tr, o, "METHOD_PARAMETER"), param_of(prov, o, m), method_owner(prov, t, m).
type_ref_scope_type(prov, tr, t) :-
type_ref_owner(prov, tr, o, "VARIABLE"), var_method(prov, o, m), method_owner(prov, t, m).
type_ref_scope_type(prov, tr, t) :- type_ref_owner(prov, tr, o, "EXPRESSION"), expr_ultimate_type(prov, o, t).

type_ref_name_dropped(prov, tr, gk) :-
type_name_out_of_scope(prov, ns, n, ar, gk),
type_ref_name(prov, tr, n), type_ref_argc(prov, tr, ar),
type_ref_scope_type(prov, tr, t), type_namespace(prov, t, ns).
expr_name_dropped(prov, e, gk) :-
type_name_out_of_scope(prov, ns, n, ar, gk),
expr_written_name(prov, e, n), expr_type_argc(prov, e, ar),
expr_ultimate_type(prov, e, t), type_namespace(prov, t, ns).
expr_callee_name_dropped(prov, e, gk) :-
type_name_out_of_scope(prov, ns, n, ar, gk),
call_callee_name(prov, e, n), call_type_argc(prov, e, ar),
expr_ultimate_type(prov, e, t), type_namespace(prov, t, ns).
heritage_name_dropped(prov, t, n, gk) :-
type_name_out_of_scope(prov, ns, n, ar, gk),
heritage_slot(prov, t, _, n, ar, ""), type_namespace(prov, t, ns).

// ── 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 @@ -266,7 +367,8 @@ type_ref_resolves(prov, tr, gk) :-
!type_ref_type_param(prov, tr, _),
type_ref_argc(prov, tr, ar),
type_ref_module(prov, tr, mod),
type_name_resolves(prov, mod, n, ar, gk).
type_name_resolves(prov, mod, n, ar, gk),
!type_ref_name_dropped(prov, tr, gk).

// The module a type reference is written in, by owner kind. Every owner kind that
// can carry a reference is listed; one that is missing makes every reference in that
Expand Down
14 changes: 14 additions & 0 deletions graph/csharp/souffle/decls_all.dl
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
.decl expr_argument(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl expr_arity(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl expr_call_candidate(c0:symbol,c1:symbol,c2:symbol)
.decl expr_callee_name_dropped(c0:symbol,c1:symbol,c2:symbol)
.decl expr_cast_ref(c0:symbol,c1:symbol,c2:symbol)
.decl expr_child(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl expr_dynamic_typed(c0:symbol,c1:symbol)
Expand All @@ -360,6 +361,7 @@
.decl expr_method_name_child(c0:symbol,c1:symbol,c2:symbol)
.decl expr_method_ref(c0:symbol,c1:symbol,c2:symbol)
.decl expr_module(c0:symbol,c1:symbol,c2:symbol)
.decl expr_name_dropped(c0:symbol,c1:symbol,c2:symbol)
.decl expr_new_type(c0:symbol,c1:symbol,c2:symbol)
.decl expr_new_type_ref(c0:symbol,c1:symbol,c2:symbol)
.decl expr_node(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
Expand Down Expand Up @@ -481,6 +483,7 @@
.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_name_dropped(c0:symbol,c1:symbol,c2:symbol,c3: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)
.decl heritage_slot(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:symbol)
Expand Down Expand Up @@ -745,6 +748,7 @@
.decl ns_encloses(c0:symbol,c1:symbol,c2:symbol)
.decl ns_exists(c0:symbol,c1:symbol)
.decl ns_prefix(c0:symbol,c1:symbol,c2:symbol)
.decl ns_read_level(c0:symbol,c1:symbol,c2:symbol,c3:number)
.decl override_cand(c0:symbol,c1:symbol,c2:symbol,c3:number)
.decl override_depth(c0:symbol,c1:symbol,c2:symbol,c3:number)
.decl override_pair(c0:symbol,c1:symbol)
Expand Down Expand Up @@ -980,10 +984,18 @@
.decl type_name_best_rank(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:number)
.decl type_name_cand(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:number)
.decl type_name_count(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:number)
.decl type_name_out_of_scope(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl type_name_predefined_unstaged(c0:symbol,c1:symbol,c2:symbol)
.decl type_name_rel_at(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl type_name_rel_src(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl type_name_resolves(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl type_name_scope_best(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:number)
.decl type_name_scope_demand(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl type_name_scope_keep(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl type_name_scope_level(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol,c5:number)
.decl type_name_unresolved(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl type_name_visible(c0:symbol,c1:symbol,c2:symbol)
.decl type_name_written_dotted(c0:symbol,c1:symbol,c2:symbol)
.decl type_namespace(c0:symbol,c1:symbol,c2:symbol)
.decl type_nest_ancestor(c0:symbol,c1:symbol,c2:symbol)
.decl type_ns(c0:symbol,c1:symbol,c2:symbol)
Expand All @@ -1009,11 +1021,13 @@
.decl type_ref_is_tuple(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_module(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_name(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_name_dropped(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_nullable(c0:symbol,c1:symbol)
.decl type_ref_owner(c0:symbol,c1:symbol,c2:symbol,c3:symbol)
.decl type_ref_parent(c0:symbol,c1:symbol,c2:symbol,c3:symbol,c4:symbol)
.decl type_ref_resolves(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_root_position(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_scope_type(c0:symbol,c1:symbol,c2:symbol)
.decl type_ref_type_param(c0:symbol,c1:symbol,c2:symbol)
.decl type_satisfies(c0:symbol,c1:symbol,c2:symbol)
.decl type_scope_key(c0:symbol,c1:symbol,c2:symbol)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{"lang": "csharp", "src": "src",
"checks": [
{"why": "inside namespace P, Models.Report is P.Models.Report",
"run": ["impact", "P.Models.Report.Make"],
"want": ["[resolved] Calls.Partially"]},
{"why": "control: a file that declares only namespace P keeps its one correct target",
"run": ["impact", "P.Models.Report.Make"],
"want": ["[resolved] Alone.Only"]},
{"why": "a child namespace of the site's is not in scope: P.Other.Models.Report is not named from P, even when the same file declares P.Other",
"run": ["impact", "P.Other.Models.Report.Make"],
"avoid": ["[resolved] Calls.Partially", "[one of a set] Calls.Partially", "Alone.Only"]},
{"why": "inside P.Other, Models is P.Other.Models: the innermost namespace with a match wins, for a nested type too",
"run": ["impact", "P.Other.Models.Report.Make"],
"want": ["[resolved] Deeper.Nearest", "[resolved] Deeper.Inner.Nested"]},
{"why": "...and the outer P.Models.Report is not also named from P.Other",
"run": ["impact", "P.Models.Report.Make"],
"avoid": ["[resolved] Deeper.Nearest", "[one of a set] Deeper.Nearest", "[resolved] Deeper.Inner.Nested", "[one of a set] Deeper.Inner.Nested"]},
{"why": "control: an unrelated namespace declared in the same file is never read",
"run": ["impact", "Z.Models.Report.Make"],
"avoid": ["[resolved] Calls.Partially", "[one of a set] Calls.Partially", "Alone.Only",
"[resolved] Deeper.Nearest", "[one of a set] Deeper.Nearest",
"[resolved] Deeper.Inner.Nested", "[one of a set] Deeper.Inner.Nested"]}]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// one file declaring several namespaces: the namespaces a name is read from are the SITE's, not the file's
namespace P.Models { public sealed class Report { public static Report Make() { return new Report(); } } }
namespace P.Other.Models { public sealed class Report { public static Report Make() { return new Report(); } } }
namespace Z.Models { public sealed class Report { public static Report Make() { return new Report(); } } }

namespace P
{
public sealed class Calls
{
// inside P, Models is P.Models; P.Other is a child of P and not in scope here
public Models.Report Partially() => Models.Report.Make();
}
}

namespace P.Other
{
public sealed class Deeper
{
// inside P.Other, Models is P.Other.Models: the innermost namespace with a match wins
public Models.Report Nearest() => Models.Report.Make();

// a nested type reads from the same namespace as the type around it
public sealed class Inner
{
public Models.Report Nested() => Models.Report.Make();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup></Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// control: a file that declares only namespace P keeps its one correct target
namespace P
{
public sealed class Alone
{
public Models.Report Only() => Models.Report.Make();
}
}
Loading