Skip to content

Commit 376f2e6

Browse files
committed
P-037 A2.2-2: constructor calls are call sites of their own (call_kind: object_creation)
Treatment-only (extractor, spec), the first call-like family under the frozen taxonomy (§10.6.2). The instrument is untouched; the after-comparison stays against R with population T. - BuildGuardedFacts emits one call fact per constructor call a handle flows into: the site is the `new` expression (explicit or target-typed), the callee is the constructor's functions[] key (`{Type}..ctor`, the spelling a constructor's own record carries), `sig` its §5.1 signature, and the arguments bind to the constructor's declared ordinals by the invocation mechanism (named arguments resolved; a params slot or ref/out argument opaque but relevant). The record carries `call_kind: object_creation`; an invocation carries no call_kind, so every A2.1 record keeps its bytes. - The invocation and constructor paths share one EmitCall: the binding mechanism is one, only the site, the callee symbol and the tag differ. - Boundary, as ruled: only the argument list binds. An object initializer is storage and a collection initializer element is container construction (named exclusions), `new` inside another call's argument stays that call's object_creation argument fact and propagates no relevance outward, array creation is not a constructor call, delegate invocation is a separate family for a separate commit. - Producer self-check: an unknown call_kind refuses the run; schema: optional `call_kind` enum on guardedCall, pinned against the producer's CallKinds by tests/test_p037_sidecar.py; spec §5.2 documents the family. Pins: - corpus/p037-shapes: ten sidecar-ctor-* shapes with a2_expect contracts — initializer, statement, expression-nested (constructor captured, the enclosing Use provably without a call fact via the new call_absent matcher), implicit new, named args, owned param, params handle; negatives nonhandle-args, object-initializer, collection-initializer. The 28 earlier shapes are byte-identical. - corpus/p037-relevance/probe: a2_2_2_observed measured; exactly the object_creation row moved to sidecar_call, delegate_invocation and every other row read as after A2.2-1; the freeze test pins that. - scripts/p037_sidecar_inertness.py: 39 documents, 75 sidecars, inert on both engines, python == rust. Refs #304 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfeUtLRm6KFRWcTGQZxQQb
1 parent cef567a commit 376f2e6

29 files changed

Lines changed: 907 additions & 47 deletions

File tree

‎corpus/p037-relevance/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ row into a checked `captured` / `excluded_by_rule:<name>` assertion.
4545
A2.2-1 landed: the transparent and may-value rows read `sidecar_call` in
4646
`a2_2_1_observed`, measured with the walker; the boxing row lost its false
4747
capture; every other row reads as at A'. A2.2-1a closed the conversion
48-
vocabulary (`reference_checked`, `boxing`) with no production change.
48+
vocabulary (`reference_checked`, `boxing`) with no production change. A2.2-2
49+
landed: the object_creation row reads `sidecar_call` in `a2_2_2_observed`;
50+
delegate_invocation and every other row read as after A2.2-1.
4951

5052
Only an A2.2 step, deliberately, one row at a time: a transparent wrapper
5153
becomes `captured` in A2.2-1, a call-like form in A2.2-2, an orphan record

‎corpus/p037-relevance/probe/expected.json‎

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"observed_at": "5a0de070af241a2612859153887ad6ae04afcce2",
66
"observed_at_note": "A' = corrected A2.1 treatment (PR #363), landed via main@23e3203; the extractor is byte-identical between A' and that merge. The superseded A (dce26ed) is not a measurement point.",
77
"a2_2_1_note": "Measured with the A2.2-1 extractor (the IOperation value-flow walker) from the tree this file is committed in, before the commit: the SHA that carries this file is the SHA it was measured at. A2.2-1 promises exactly the transparent and may-value rows move to sidecar_call; every other row must read as it did at A'. A2.2-1a added the CheckedRef and Boxing rows, measured at both points.",
8+
"a2_2_2_note": "Measured with the A2.2-2 extractor (constructor calls as call-like sites) from the tree this file is committed in. A2.2-2 promises exactly the object_creation row(s) move to sidecar_call; every other row, delegate_invocation included, must read as it did after A2.2-1.",
89
"observed_vocabulary": {
910
"sidecar_call": "a function record with a guarded_facts call fact for the probe call",
1011
"record_without_call_fact": "a function record exists but carries no call fact for the probe call",
@@ -31,195 +32,222 @@
3132
"class": "direct",
3233
"conversion": "reference_upcast",
3334
"a2_1_observed": "sidecar_call",
34-
"a2_2_1_observed": "sidecar_call"
35+
"a2_2_1_observed": "sidecar_call",
36+
"a2_2_2_observed": "sidecar_call"
3537
},
3638
"PlainSame": {
3739
"class": "direct",
3840
"conversion": "none",
3941
"note": "identity binding to a NON-consuming callee: the legacy pass reads the pass as an escape, no tracked local remains, no record at all; the orphan carrier's case (\u00a710.6.6), and a direct flow",
4042
"a2_1_observed": "no_record",
41-
"a2_2_1_observed": "no_record"
43+
"a2_2_1_observed": "no_record",
44+
"a2_2_2_observed": "no_record"
4245
},
4346
"Parens": {
4447
"class": "transparent",
4548
"wrapper": "parenthesized",
4649
"conversion": "reference_upcast",
4750
"a2_1_observed": "sidecar_call",
48-
"a2_2_1_observed": "sidecar_call"
51+
"a2_2_1_observed": "sidecar_call",
52+
"a2_2_2_observed": "sidecar_call"
4953
},
5054
"IdentityCast": {
5155
"class": "transparent",
5256
"wrapper": "cast",
5357
"conversion": "identity",
5458
"a2_1_observed": "record_without_call_fact",
55-
"a2_2_1_observed": "sidecar_call"
59+
"a2_2_1_observed": "sidecar_call",
60+
"a2_2_2_observed": "sidecar_call"
5661
},
5762
"Cast": {
5863
"class": "transparent",
5964
"wrapper": "cast",
6065
"conversion": "reference_upcast",
6166
"a2_1_observed": "record_without_call_fact",
62-
"a2_2_1_observed": "sidecar_call"
67+
"a2_2_1_observed": "sidecar_call",
68+
"a2_2_2_observed": "sidecar_call"
6369
},
6470
"AsCast": {
6571
"class": "transparent",
6672
"wrapper": "as",
6773
"conversion": "reference_upcast",
6874
"a2_1_observed": "record_without_call_fact",
69-
"a2_2_1_observed": "sidecar_call"
75+
"a2_2_1_observed": "sidecar_call",
76+
"a2_2_2_observed": "sidecar_call"
7077
},
7178
"Bang": {
7279
"class": "transparent",
7380
"wrapper": "null_forgiving",
7481
"conversion": "reference_upcast",
7582
"a2_1_observed": "record_without_call_fact",
76-
"a2_2_1_observed": "sidecar_call"
83+
"a2_2_1_observed": "sidecar_call",
84+
"a2_2_2_observed": "sidecar_call"
7785
},
7886
"CheckedRef": {
7987
"class": "transparent",
8088
"wrapper": "cast",
8189
"conversion": "reference_checked",
8290
"note": "explicit reference conversion on a Base-typed handle: the same reference or no call at all (the cast throws); never an alternate value, unlike `as`",
8391
"a2_1_observed": "record_without_call_fact",
84-
"a2_2_1_observed": "sidecar_call"
92+
"a2_2_1_observed": "sidecar_call",
93+
"a2_2_2_observed": "sidecar_call"
8594
},
8695
"Ternary": {
8796
"class": "may_value",
8897
"form": "conditional",
8998
"conversion": "reference_upcast",
9099
"a2_1_observed": "record_without_call_fact",
91-
"a2_2_1_observed": "sidecar_call"
100+
"a2_2_1_observed": "sidecar_call",
101+
"a2_2_2_observed": "sidecar_call"
92102
},
93103
"Coalesce": {
94104
"class": "may_value",
95105
"form": "null_coalescing",
96106
"conversion": "reference_upcast",
97107
"a2_1_observed": "record_without_call_fact",
98-
"a2_2_1_observed": "sidecar_call"
108+
"a2_2_1_observed": "sidecar_call",
109+
"a2_2_2_observed": "sidecar_call"
99110
},
100111
"Switch": {
101112
"class": "may_value",
102113
"form": "switch_expression",
103114
"conversion": "reference_upcast",
104115
"a2_1_observed": "record_without_call_fact",
105-
"a2_2_1_observed": "sidecar_call"
116+
"a2_2_1_observed": "sidecar_call",
117+
"a2_2_2_observed": "sidecar_call"
106118
},
107119
"AsMayFail": {
108120
"class": "may_value",
109121
"form": "as_may_fail",
110122
"conversion": "may_fail_null",
111123
"a2_1_observed": "record_without_call_fact",
112-
"a2_2_1_observed": "sidecar_call"
124+
"a2_2_1_observed": "sidecar_call",
125+
"a2_2_2_observed": "sidecar_call"
113126
},
114127
"Ctor": {
115128
"class": "call_like",
116129
"form": "object_creation",
117130
"conversion": "reference_upcast",
118131
"a2_1_observed": "record_without_call_fact",
119-
"a2_2_1_observed": "record_without_call_fact"
132+
"a2_2_1_observed": "record_without_call_fact",
133+
"a2_2_2_observed": "sidecar_call"
120134
},
121135
"DelegateCall": {
122136
"class": "call_like",
123137
"form": "delegate_invocation",
124138
"conversion": "reference_upcast",
125139
"a2_1_observed": "no_record",
126-
"a2_2_1_observed": "no_record"
140+
"a2_2_1_observed": "no_record",
141+
"a2_2_2_observed": "no_record"
127142
},
128143
"Nested": {
129144
"class": "indirect",
130145
"exclusion": "nested_call_result",
131146
"conversion": "not_applicable",
132147
"note": "Wrap(r) itself is direct and is owed its own call fact; Use receives call_result",
133148
"a2_1_observed": "no_record",
134-
"a2_2_1_observed": "no_record"
149+
"a2_2_1_observed": "no_record",
150+
"a2_2_2_observed": "no_record"
135151
},
136152
"ArrayInit": {
137153
"class": "indirect",
138154
"exclusion": "container_construction",
139155
"conversion": "not_applicable",
140156
"a2_1_observed": "record_without_call_fact",
141-
"a2_2_1_observed": "record_without_call_fact"
157+
"a2_2_1_observed": "record_without_call_fact",
158+
"a2_2_2_observed": "record_without_call_fact"
142159
},
143160
"CollectionInit": {
144161
"class": "indirect",
145162
"exclusion": "container_construction",
146163
"conversion": "not_applicable",
147164
"a2_1_observed": "record_without_call_fact",
148-
"a2_2_1_observed": "record_without_call_fact"
165+
"a2_2_1_observed": "record_without_call_fact",
166+
"a2_2_2_observed": "record_without_call_fact"
149167
},
150168
"Tuple": {
151169
"class": "indirect",
152170
"exclusion": "tuple_construction",
153171
"conversion": "not_applicable",
154172
"a2_1_observed": "no_record",
155-
"a2_2_1_observed": "no_record"
173+
"a2_2_1_observed": "no_record",
174+
"a2_2_2_observed": "no_record"
156175
},
157176
"Closure": {
158177
"class": "indirect",
159178
"exclusion": "closure_capture",
160179
"conversion": "not_applicable",
161180
"note": "the inner Use(r) belongs to the lambda body, which has no record of its own (known gap)",
162181
"a2_1_observed": "no_record",
163-
"a2_2_1_observed": "no_record"
182+
"a2_2_1_observed": "no_record",
183+
"a2_2_2_observed": "no_record"
164184
},
165185
"MethodGroup": {
166186
"class": "indirect",
167187
"exclusion": "method_group_conversion",
168188
"conversion": "not_applicable",
169189
"a2_1_observed": "record_without_call_fact",
170-
"a2_2_1_observed": "record_without_call_fact"
190+
"a2_2_1_observed": "record_without_call_fact",
191+
"a2_2_2_observed": "record_without_call_fact"
171192
},
172193
"UserConversion": {
173194
"class": "indirect",
174195
"exclusion": "user_conversion",
175196
"conversion": "user_defined_implicit",
176197
"note": "syntactically a bare identifier; only the semantic model shows the hidden op_Implicit",
177198
"a2_1_observed": "no_record",
178-
"a2_2_1_observed": "no_record"
199+
"a2_2_1_observed": "no_record",
200+
"a2_2_2_observed": "no_record"
179201
},
180202
"ExplicitUserConversion": {
181203
"class": "indirect",
182204
"exclusion": "user_conversion",
183205
"conversion": "user_defined_explicit",
184206
"note": "syntactically a cast; the user-defined operator sits inside the cast expression, and (XBox)r already has type XBox at the call",
185207
"a2_1_observed": "record_without_call_fact",
186-
"a2_2_1_observed": "record_without_call_fact"
208+
"a2_2_1_observed": "record_without_call_fact",
209+
"a2_2_2_observed": "record_without_call_fact"
187210
},
188211
"NameOf": {
189212
"class": "indirect",
190213
"exclusion": "nameof_operand",
191214
"conversion": "not_applicable",
192215
"a2_1_observed": "record_without_call_fact",
193-
"a2_2_1_observed": "record_without_call_fact"
216+
"a2_2_1_observed": "record_without_call_fact",
217+
"a2_2_2_observed": "record_without_call_fact"
194218
},
195219
"MemberAccess": {
196220
"class": "indirect",
197221
"exclusion": "member_access_on_handle",
198222
"conversion": "not_applicable",
199223
"a2_1_observed": "record_without_call_fact",
200-
"a2_2_1_observed": "record_without_call_fact"
224+
"a2_2_1_observed": "record_without_call_fact",
225+
"a2_2_2_observed": "record_without_call_fact"
201226
},
202227
"Boxing": {
203228
"class": "indirect",
204229
"exclusion": "boxing_conversion",
205230
"conversion": "boxing",
206231
"note": "an owned disposable STRUCT parameter passed to object: the callee receives a boxed copy, not the handle; owned so the record exists regardless of legacy escape admission",
207232
"a2_1_observed": "sidecar_call",
208-
"a2_2_1_observed": "record_without_call_fact"
233+
"a2_2_1_observed": "record_without_call_fact",
234+
"a2_2_2_observed": "record_without_call_fact"
209235
},
210236
"Receiver": {
211237
"class": "indirect",
212238
"exclusion": "receiver_not_summary_parameter",
213239
"conversion": "not_applicable",
214240
"a2_1_observed": "record_without_call_fact",
215-
"a2_2_1_observed": "record_without_call_fact"
241+
"a2_2_1_observed": "record_without_call_fact",
242+
"a2_2_2_observed": "record_without_call_fact"
216243
},
217244
"Indexer": {
218245
"class": "indirect",
219246
"exclusion": "storage_assignment",
220247
"conversion": "not_applicable",
221248
"a2_1_observed": "no_record",
222-
"a2_2_1_observed": "no_record"
249+
"a2_2_1_observed": "no_record",
250+
"a2_2_2_observed": "no_record"
223251
}
224252
}
225253
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
5+
// A2.2-2 NEGATIVE: a collection initializer element is container_construction, not a
6+
// constructor argument; the parameterless `new List<Stream>()` binds nothing.
7+
static class ShapeCtorCollectionInitializer
8+
{
9+
static void Caller(string path)
10+
{
11+
var keep = File.OpenRead(path);
12+
var s = File.OpenRead(path);
13+
var l = new List<Stream> { s };
14+
keep.Dispose();
15+
}
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"schema": "p037-fact-shape/1",
3+
"shape": "sidecar-ctor-collection-initializer",
4+
"status": "anchored",
5+
"why": "P-037 A2.2-2 (formal note §10.6.2, call-like object_creation): negative: a collection initializer element is not a constructor argument. A row that moves without a step meaning to move it is a regression.",
6+
"a2_expect": [
7+
{
8+
"function": "ShapeCtorCollectionInitializer.Caller",
9+
"guarded_facts": "absent"
10+
}
11+
],
12+
"facts": {
13+
"ShapeCtorCollectionInitializer.Caller": {
14+
"params": null,
15+
"body": [
16+
"acquire:keep@11",
17+
"acquire:s@12",
18+
"release:keep@14"
19+
],
20+
"guarded_facts": null
21+
}
22+
},
23+
"verdict": {
24+
"rust": [
25+
"OWN001:warning@12"
26+
],
27+
"python": [
28+
"OWN001:warning@12"
29+
]
30+
}
31+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.IO;
3+
4+
// A2.2-2: `Use(new Wrapper(s, true))`. The constructor call is relevant (s flows into it) and gets
5+
// its own call fact with form `expression`; `Use` receives an `object_creation` argument, which
6+
// is NOT a handle, so `Use` gets no call fact: nested handles never propagate relevance outward.
7+
static class ShapeCtorExpressionNested
8+
{
9+
sealed class Wrapper : IDisposable { public Wrapper(Stream s, bool leaveOpen) { } public void Dispose() { } }
10+
static void Use(Wrapper w) { }
11+
static void Caller(string path)
12+
{
13+
var keep = File.OpenRead(path);
14+
var s = File.OpenRead(path);
15+
Use(new Wrapper(s, true));
16+
keep.Dispose();
17+
}
18+
}

0 commit comments

Comments
 (0)