Skip to content
← Back

src/analysis/narrowing_inlay_merger.ghul

1
namespace Analysis is
2
use Logging
3
use Collections
4
5
// Merges editor-only narrowing-introduction inlays that share a source
6
// location and construct into a single hint. The stored carriers hold
7
// only the narrowed-to type in `detail`; the hover body is assembled
8
// here so a true/false edge pair renders as one marker rather than two
9
// stacked at one column. The body is presentation-neutral text — one
10
// `► <type>` line for the matched narrow, plus a `> <type>` line for
11
// the complementary else narrow when there is one. A client wraps it
12
// for display (the VS Code hover fences it as a ghul code block so the
13
// types syntax-highlight and the sigils stay neutral).
14
//
15
// The matched edge carries the bare `narrowing-<kind>` slug; the
16
// complementary edge carries `narrowing-<kind>-complement`, at the same
17
// location. Kill hints (a different sigil) and any other carrier pass
18
// through unchanged.
19
class NARROWING_INLAY_MERGER is
20
init() is si
21
22
merge(carriers: Iterable[INLAY]) -> LIST[INLAY] is
23
let groups = MAP[string, LIST[INLAY]]()
24
let order = LIST[string]()
25
26
let other_index mut = 0
27
28
for c in carriers do
29
let key: string mut = ""
30
31
if _is_introduction(c) then
32
key = "I\t{c.location.start_line}\t{c.location.start_column}\t{_base_code(c.code)}"
33
else
34
key = "X\t{other_index}"
35
other_index = other_index + 1
36
fi
37
38
if !groups.contains_key(key) then
39
order.add(key)
40
groups[key] = LIST[INLAY]()
41
fi
42
43
groups[key].add(c)
44
od
45
46
let result = LIST[INLAY]()
47
48
for key in order do
49
let group = groups[key]
50
51
if group.count > 0 /\ _is_introduction(group[0]) then
52
result.add(_render(group))
53
else
54
for c in group do
55
result.add(c)
56
od
57
fi
58
od
59
60
return result
61
si
62
63
_render(group: LIST[INLAY]) -> INLAY is
64
let head = group[0]
65
let base = _base_code(head.code)
66
67
let true_detail mut = ""
68
let false_detail: string? mut = null
69
70
for g in group do
71
if _is_complement(g.code) then
72
false_detail = g.detail
73
else
74
true_detail = g.detail ?? ""
75
fi
76
od
77
78
let body = System.Text.StringBuilder()
79
80
body.append("► ").append(_body_text(true_detail))
81
82
if false_detail? then
83
body.append("\n> ").append(_body_text(false_detail))
84
fi
85
86
return INLAY(
87
head.is_analysis,
88
head.is_compile_expressions,
89
head.location,
90
base,
91
head.text,
92
body.to_string()
93
)
94
si
95
96
// A path narrow whose static type couldn't be resolved stores an
97
// empty type; fall back to a bare phrase so the line is not left
98
// dangling after its sigil.
99
_body_text(detail: string?) -> string =>
100
if detail? /\ detail.length > 0 then detail else "holds a value" fi
101
102
_is_introduction(c: INLAY) -> bool =>
103
c.text =~ "►"
104
105
_is_complement(code: string?) -> bool =>
106
code? /\ code.ends_with("-complement")
107
108
_base_code(code: string?) -> string =>
109
if !code? then
110
""
111
elif code.ends_with("-complement") then
112
code.substring(0, code.length - "-complement".length)
113
else
114
code
115
fi
116
si
117
si