diff --git a/lib/loc.ml b/lib/loc.ml index ebe043e..6815848 100644 --- a/lib/loc.ml +++ b/lib/loc.ml @@ -20,11 +20,28 @@ type t = { col : int; (* 1-based *) eline : int; (* 1-based, exclusive end *) ecol : int; + (* The macro whose expansion produced whatever is at this position, if one + did. It rides on the location rather than on the form because the location + is the thing that already travels: [Expand.unmarshal] stamps the call + site onto every node a macro answers with, and that stamp goes on through + the AST and the typed IR untouched. Tagging it here means an error raised + anywhere downstream can say which macro it is really about, with no field + added to Form, to Ast or to Tast. *) + macro : string option; } -let make file line col = { file; line; col; eline = line; ecol = col } +let make file line col = + { file; line; col; eline = line; ecol = col; macro = None } + let unknown = make "" 0 0 +(** Tag a location as coming out of [name]'s expansion, unless it already names + a macro. Already-tagged wins because the tag is applied outermost-last: the + macro the author actually wrote is the one worth naming, not whatever it + expanded into on the way. *) +let from_macro name (t : t) = + match t.macro with None -> { t with macro = Some name } | Some _ -> t + (** [upto start stop] is [start] widened to end where [stop] begins. A [stop] that is not after [start], or is in another file, leaves it alone: a span that runs backwards would draw nonsense. *) @@ -124,6 +141,14 @@ let sort_notes (d : diag) = let note ?(sev = Info) loc msg = { nmsg = msg; nloc = loc; nsev = sev } let diag ?(kind = "error") ?(notes = []) ?expansion loc msg = + (* The location already knows whether it came out of a macro, so an error + does not have to be raised anywhere special to say so. An explicit + [expansion] still wins, for the one caller that knows better. *) + let expansion = + match expansion with + | Some _ as e -> e + | None -> (match loc.macro with Some m -> Some (m, loc) | None -> None) + in sort_notes { kind; dloc = loc; dmsg = msg; notes; expansion } let raise_diag d = raise (Error (sort_notes d)) diff --git a/lib/macro.ml b/lib/macro.ml index 8ff2627..de18afd 100644 --- a/lib/macro.ml +++ b/lib/macro.ml @@ -176,7 +176,12 @@ let rec expand_form (l : loaded) (f : Form.t) : Form.t = match f.Form.v with | Form.List ({ Form.v = Form.Sym n; _ } :: args) when List.mem_assoc n l.fns -> let args = List.map (expand_form l) args in - settle l n loc (Expand.call ~loc (List.assoc n l.fns) args) fuel + (* The call site, tagged with the macro it is a call to. [Expand.unmarshal] + stamps this onto every node the macro answers with, so from here down + every form it produced knows where it came from and an error on one of + them can say so. *) + let from = Loc.from_macro n loc in + settle l n loc (Expand.call ~loc:from (List.assoc n l.fns) args) fuel | Form.List xs -> Form.make (Form.List (List.map (expand_form l) xs)) loc | Form.Vec xs -> Form.make (Form.Vec (List.map (expand_form l) xs)) loc | Form.Map xs -> Form.make (Form.Map (List.map (expand_form l) xs)) loc @@ -193,7 +198,9 @@ and settle l first loc (f : Form.t) left = first fuel else begin let args = List.map (expand_form l) args in - settle l first loc (Expand.call ~loc (List.assoc m l.fns) args) (left - 1) + let from = Loc.from_macro m loc in + settle l first loc (Expand.call ~loc:from (List.assoc m l.fns) args) + (left - 1) end (* Settled at the head. The rest of it may still hold macro calls — a cond expands to an if whose else-branch is another cond — so the ordinary walk