1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
use super::def_member_facts::member_facts;
use super::*;
// --- Foreach element type fact -----------------------------------
#[test]
fn ds0011_var_over_a_generic_collection_resolves_the_single_type_argument() {
let e = extract_src(
r#"
namespace App.ForEachVar;
public class Host
{
private List<Widget> _field;
public void Run(IEnumerable<Widget> param)
{
List<Widget> local = null;
foreach (var a in _field) { a.Go(); }
foreach (var b in param) { b.Go(); }
foreach (var c in local) { c.Go(); }
}
}
"#,
);
// A field, a parameter and a local: the same lookup the call
// hop uses for its qualifier, reading whichever table vouches for the
// collection's name.
assert_eq!(
member_facts(&e),
vec![
("Go", Some("Widget")),
("Go", Some("Widget")),
("Go", Some("Widget"))
]
);
}
#[test]
fn ds0011_a_var_foreach_stays_unknown_unless_the_collection_is_a_bare_identifier_with_one_type_argument(
) {
let e = extract_src(
r#"
namespace App.ForEachUnknown;
public class Host
{
private Widget[] _array;
private Dictionary<string, Widget> _pair;
private Map map;
public void Run<T>(List<T> generic)
{
foreach (var a in GetItems()) { a.Go(); }
foreach (var b in map.Items) { b.Go(); }
foreach (var c in _array) { c.Go(); }
foreach (var d in _pair) { d.Go(); }
foreach (var f in generic) { f.Go(); }
}
private List<Widget> GetItems() { return null; }
}
"#,
);
// A call, a dotted chain, an array (no top-level type-argument list
// at all), a two-argument generic (not a SINGLE type argument), and a
// one-argument generic whose argument is the method's own type
// parameter (the wildcard descriptor, "*" -- nothing at this site
// knows what it is bound to) all leave the loop variable exactly as
// taken-but-unknown as every other unresolvable local. Filtered to
// `Go` alone: `map.Items` is itself an ordinary tier-(e) member
// access on the FIELD `map` (declared type `Map`), unrelated to this
// ticket, so it earns its own unaffected receiver fact.
let go_facts: Vec<_> = e
.refs
.iter()
.filter(|r| r.member.as_deref() == Some("Go"))
.map(|r| r.receiver_type.as_deref())
.collect();
assert_eq!(go_facts, vec![None, None, None, None, None]);
}
#[test]
fn ds0011_a_foreach_variable_used_as_another_foreachs_collection_is_never_a_fact() {
let e = extract_src(
r#"
namespace App.ForEachChain;
public class Host
{
private List<Widget> _bag;
public void Nested()
{
foreach (var outer in _bag)
{
outer.Go();
foreach (var inner in outer) { inner.Go(); }
}
}
}
"#,
);
// `outer` resolves to a REAL fact (`_bag`'s single type argument), but
// that derived fact never carries a type argument of its own (see
// `collection_element_fact`), so `inner` -- one nested hop further --
// finds no single argument to read and stays taken-but-unknown. One
// hop, never a chain, structurally: a foreach element fact can be a
// RECEIVER but never itself a collection another `var` derives from.
assert_eq!(member_facts(&e), vec![("Go", Some("Widget")), ("Go", None)]);
}
#[test]
fn ds0011_a_foreach_variable_conflicting_with_another_declaration_stays_taken_but_unknown() {
let e = extract_src(
r#"
namespace App.ForEachConflict;
public class Host
{
private List<Widget> made;
public void Run(bool flag)
{
if (flag) { foreach (var made in Gadgets()) { made.Go(); } }
else { Widget made = new Widget(); made.Go(); }
}
private List<Gadget> Gadgets() { return null; }
}
"#,
);
// `made` is declared twice with conflicting shapes in sibling blocks
// of the SAME method (a foreach-derived local in one arm, an
// explicit local in the other): the flat member table collapses both
// to the same taken-but-unknown slot -- the ordinary `add_fact`
// conflict rule, unchanged here -- with no fall-through to
// the enclosing field of the same name.
assert_eq!(member_facts(&e), vec![("Go", None), ("Go", None)]);
}
#[test]
fn ds0011_a_destructuring_foreach_variable_is_left_alone() {
// `foreach (var (a, b) in pairs)` has no single name for the element
// fact to name -- deliberately left without a fact, and its
// absence must not disturb an ordinary sibling declaration's own fact.
let e = extract_src(
r#"
namespace App.ForEachTuple;
public class Host
{
public void Run(List<(int, int)> pairs)
{
Widget w = new Widget();
foreach (var (a, b) in pairs) { }
w.Go();
}
}
"#,
);
assert_eq!(member_facts(&e), vec![("Go", Some("Widget"))]);
}