Skip to content

Commit 50b0e12

Browse files
authored
feat: introduce Provenance type and migrate metadata from FileRange (#1140)
Fixes #1139 Introduces a `Provenance` type that fully replaces `FileRange` and `SourceRange` as the canonical way to store source locations in metadata. This structurally eliminates the "SourceRange without file name" problem. The `Provenance` type has two constructors: - `Provenance.loc uri range` — a real source location (always requires both URI and range) - `Provenance.synthesized origin` — a node created programmatically, with a `SynthesizedOrigin` inductive type A `SynthesizedOrigin` inductive enforces that only canonical origins are used (`smtEncode`, `nondetIte`, `laurelParse`, `laurel`, `laurelToCore`, `structuredToUnstructured`), preventing typos and duplicates at the type level. Key changes: - The `.fileRange` variant is removed from `MetaDataElem.Value` — all metadata values now use `.provenance` exclusively - `MetaData.ofSourceRange` emits only a provenance element - `getProvenance` is the single source of truth for reading source locations from metadata - `setCallSiteFileRange` works directly with `Provenance` instead of roundtripping through `FileRange` - `getFileRange` delegates to `getProvenance` for extraction - `FileRange.unknown` and `SourceRange.none` eliminated from metadata construction - SMT DDM translator uses `smtAnn` combinator to reduce annotation boilerplate - SARIF output uses `getFileRange` (which reads provenance) `FileRange` remains as a utility struct for extraction and formatting (e.g., in `DiagnosticModel`), but is no longer a metadata value type. Existing tests pass unchanged. ## Follow-up - Migrate the B3, Boole, and Python grammar ASTs from using `SourceRange` as their annotation type parameter to `Provenance`. This would allow combining multiple files at the AST level and enable proper provenance tracking through translation passes.
1 parent a5d36ed commit 50b0e12

18 files changed

Lines changed: 265 additions & 185 deletions

File tree

Strata/Backends/CBMC/GOTO/CoreToCProverGOTO.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ def convertMetaData (md : Imperative.MetaData Core.Expression)
108108
match elem.fld with
109109
| .label l => match elem.value with
110110
| .msg s => some ⟨.label l, .msg s⟩
111-
| .fileRange r => some ⟨.label l, .fileRange r⟩
112111
| .switch b => some ⟨.label l, .switch b⟩
112+
| .provenance p => some ⟨.label l, .provenance p⟩
113113
| .expr _ => none
114114
| .var _ => none
115115

Strata/DL/Imperative/MetaData.lean

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ module
88
public import Strata.DL.Imperative.PureExpr
99
public import Strata.DL.Util.DecidableEq
1010
public import Strata.Util.FileRange
11+
public import Strata.Util.Provenance
1112

1213
namespace Imperative
13-
open Strata
14+
open Strata (DiagnosticModel DiagnosticType FileRange Provenance Uri SourceRange)
1415

1516
public section
1617

@@ -69,40 +70,40 @@ instance [Repr P.Ident] : Repr (MetaDataElem.Field P) where
6970
| .label s => f!"MetaDataElem.Field.label {s}"
7071
Repr.addAppParen res prec
7172

72-
/-- A metadata value, which can be either an expression, a message, or a fileRange -/
73+
/-- A metadata value, which can be either an expression, a message, a switch, or a provenance. -/
7374
inductive MetaDataElem.Value (P : PureExpr) where
7475
/-- Metadata value in the form of a structured expression. -/
7576
| expr (e : P.Expr)
7677
/-- Metadata value in the form of an arbitrary string. -/
7778
| msg (s : String)
78-
/-- Metadata value in the form of a fileRange. -/
79-
| fileRange (r: FileRange)
8079
/-- Metadata value in the form of a boolean switch. -/
8180
| switch (b : Bool)
81+
/-- Metadata value in the form of a provenance (source location or synthesized origin). -/
82+
| provenance (p : Provenance)
8283

8384
instance [ToFormat P.Expr] : ToFormat (MetaDataElem.Value P) where
8485
format f := match f with
8586
| .expr e => f!"{e}"
8687
| .msg s => f!"{s}"
87-
| .fileRange r => f!"{r}"
8888
| .switch b => f!"{b}"
89+
| .provenance p => f!"{p}"
8990

9091
instance [Repr P.Expr] : Repr (MetaDataElem.Value P) where
9192
reprPrec v prec :=
9293
let res :=
9394
match v with
9495
| .expr e => f!".expr {reprPrec e prec}"
9596
| .msg s => f!".msg {s}"
96-
| .fileRange fr => f!".fileRange {fr}"
9797
| .switch b => f!".switch {repr b}"
98+
| .provenance p => f!".provenance {repr p}"
9899
Repr.addAppParen res prec
99100

100101
def MetaDataElem.Value.beq [BEq P.Expr] (v1 v2 : MetaDataElem.Value P) :=
101102
match v1, v2 with
102103
| .expr e1, .expr e2 => e1 == e2
103104
| .msg m1, .msg m2 => m1 == m2
104-
| .fileRange r1, .fileRange r2 => r1 == r2
105105
| .switch b1, .switch b2 => b1 == b2
106+
| .provenance p1, .provenance p2 => p1 == p2
106107
| _, _ => false
107108

108109
instance [BEq P.Expr] : BEq (MetaDataElem.Value P) where
@@ -178,7 +179,8 @@ instance [Repr P.Expr] [Repr P.Ident] : Repr (MetaDataElem P) where
178179
/-! ### Common metadata fields -/
179180

180181
@[match_pattern]
181-
abbrev MetaData.fileRange : MetaDataElem.Field P := .label "fileRange"
182+
abbrev MetaData.provenanceField : MetaDataElem.Field P := .label "provenance"
183+
182184
@[match_pattern]
183185
abbrev MetaData.reachCheck : MetaDataElem.Field P := .label "reachCheck"
184186
@[match_pattern]
@@ -222,18 +224,31 @@ def MetaData.hasSatisfiabilityCheck {P : PureExpr} [BEq P.Ident] (md : MetaData
222224
| _ => false
223225
| none => false
224226

227+
/-- Get the provenance from metadata. -/
228+
def getProvenance {P : PureExpr} [BEq P.Ident] (md : MetaData P) : Option Provenance := do
229+
let elem ← md.findElem Imperative.MetaData.provenanceField
230+
match elem.value with
231+
| .provenance p => some p
232+
| _ => none
233+
225234
def getFileRange {P : PureExpr} [BEq P.Ident] (md: MetaData P) : Option FileRange := do
226-
let fileRangeElement <- md.findElem Imperative.MetaData.fileRange
227-
match fileRangeElement.value with
228-
| .fileRange fileRange =>
229-
some fileRange
230-
| _ => none
235+
let p ← getProvenance md
236+
p.toFileRange
237+
238+
/-- Create metadata with a provenance element. -/
239+
def MetaData.ofProvenance {P : PureExpr} (p : Provenance) : MetaData P :=
240+
#[{ fld := MetaData.provenanceField, value := .provenance p }]
241+
242+
/-- Create metadata from a source range and URI, storing provenance. -/
243+
def MetaData.ofSourceRange {P : PureExpr} (uri : Uri) (sr : SourceRange) : MetaData P :=
244+
MetaData.ofProvenance (Provenance.ofSourceRange uri sr)
231245

232246
/-- Create a DiagnosticModel from metadata and a message.
233-
Uses the file range from metadata if available, otherwise uses a default location. -/
247+
Uses provenance or file range from metadata if available, otherwise uses a default location. -/
234248
def MetaData.toDiagnostic {P : PureExpr} [BEq P.Ident] (md : MetaData P) (msg : String) (type : DiagnosticType := DiagnosticType.UserError): DiagnosticModel :=
235-
match getFileRange md with
236-
| some fr => DiagnosticModel.withRange fr msg type
249+
match getProvenance md with
250+
| some (.loc uri range) => DiagnosticModel.withRange { file := uri, range } msg type
251+
| some (.synthesized _) => DiagnosticModel.fromMessage msg type
237252
| none => DiagnosticModel.fromMessage msg type
238253

239254
/-- Create a DiagnosticModel from metadata and a Format message. -/
@@ -261,7 +276,16 @@ def getRelatedFileRanges {P : PureExpr} [BEq P.Ident] (md: MetaData P) : Array F
261276
md.filterMap fun elem =>
262277
if elem.fld == Imperative.MetaData.relatedFileRange then
263278
match elem.value with
264-
| .fileRange fr => some fr
279+
| .provenance p => p.toFileRange
280+
| _ => none
281+
else none
282+
283+
/-- Get all related provenances from metadata, in order. -/
284+
private def getRelatedProvenances {P : PureExpr} [BEq P.Ident] (md: MetaData P) : Array Provenance :=
285+
md.filterMap fun elem =>
286+
if elem.fld == Imperative.MetaData.relatedFileRange then
287+
match elem.value with
288+
| .provenance p => some p
265289
| _ => none
266290
else none
267291

@@ -270,20 +294,21 @@ def MetaData.eraseAllElems {P : PureExpr} [BEq P.Ident]
270294
(md : MetaData P) (fld : MetaDataElem.Field P) : MetaData P :=
271295
md.filter (fun e => !(e.fld == fld))
272296

273-
/-- Replace the primary file range with a new one, shifting existing related
274-
file ranges and prepending the old primary range. -/
297+
/-- Replace the primary provenance with a new one, shifting existing related
298+
provenances and prepending the old primary provenance. -/
275299
def MetaData.setCallSiteFileRange {P : PureExpr} [BEq P.Ident]
276300
(md : MetaData P) (callSiteRange : MetaData P) : MetaData P :=
277-
match getFileRange callSiteRange, getFileRange md with
278-
| some csRange, some origRange =>
279-
let existingRelated := getRelatedFileRanges md
280-
let md := md.eraseElem MetaData.fileRange
301+
match getProvenance callSiteRange, getProvenance md with
302+
| some csProv, some origProv =>
303+
let existingRelated := getRelatedProvenances md
304+
let md := md.eraseElem MetaData.provenanceField
281305
let md := md.eraseAllElems MetaData.relatedFileRange
282-
let md := md.pushElem MetaData.fileRange (.fileRange csRange)
283-
let md := md.pushElem MetaData.relatedFileRange (.fileRange origRange)
284-
existingRelated.foldl (fun md fr => md.pushElem MetaData.relatedFileRange (.fileRange fr)) md
285-
| some csRange, none =>
286-
md.pushElem MetaData.fileRange (.fileRange csRange)
306+
let md := md.pushElem MetaData.provenanceField (.provenance csProv)
307+
let md := md.pushElem MetaData.relatedFileRange (.provenance origProv)
308+
existingRelated.foldl (fun md p => md.pushElem MetaData.relatedFileRange (.provenance p)) md
309+
| some csProv, none =>
310+
let md := md.eraseElem MetaData.provenanceField
311+
md.pushElem MetaData.provenanceField (.provenance csProv)
287312
| none, _ => md
288313

289314
/-- Metadata field for property type classification (e.g., "divisionByZero"). -/

0 commit comments

Comments
 (0)