Skip to content

Commit 69d7b67

Browse files
brielovclaude
andcommitted
Extend LSP hover to lambda params, constructors, and qualified names
- Add paramSpan to CAbs for lambda parameter hover support - Track constructor definitions/references via constructorIds map - Add moduleSpan/memberSpan to CVar for qualified name hover (e.g., String.concat) - Add fieldSpan to SField for member access tracking - Fix basics test: use single quotes for char literals ('a' not "a") 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9aeb96c commit 69d7b67

7 files changed

Lines changed: 212 additions & 65 deletions

File tree

integration/basics/main.alg

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,10 @@ let testCons =
258258
-- =============================================================================
259259

260260
let testChars =
261-
let c1 = "a" in
262-
let c2 = "Z" in
263-
let c3 = "5" in
264-
let c4 = " " in
261+
let c1 = 'a' in
262+
let c2 = 'Z' in
263+
let c3 = '5' in
264+
let c4 = ' ' in
265265
let a = if Char.isAlpha c1 then 1 else 0 in -- 1
266266
let b = if Char.isAlpha c3 then 1 else 0 in -- 0
267267
let c = if Char.isDigit c3 then 1 else 0 in -- 1

src/compile.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ const offsetExpr = (expr: SExpr, offset: number): SExpr => {
410410
case "SAbs":
411411
return {
412412
...expr,
413+
params: expr.params.map((p) => ({ ...p, span: offsetSpan(p.span, offset) })),
413414
body: offsetExpr(expr.body, offset),
414415
span: offsetSpan(expr.span, offset),
415416
};
@@ -419,11 +420,16 @@ const offsetExpr = (expr: SExpr, offset: number): SExpr => {
419420
value: offsetExpr(expr.value, offset),
420421
body: offsetExpr(expr.body, offset),
421422
span: offsetSpan(expr.span, offset),
423+
nameSpan: offsetSpan(expr.nameSpan, offset),
422424
};
423425
case "SLetRec":
424426
return {
425427
...expr,
426-
bindings: expr.bindings.map((b) => ({ ...b, value: offsetExpr(b.value, offset) })),
428+
bindings: expr.bindings.map((b) => ({
429+
...b,
430+
value: offsetExpr(b.value, offset),
431+
nameSpan: offsetSpan(b.nameSpan, offset),
432+
})),
427433
body: offsetExpr(expr.body, offset),
428434
span: offsetSpan(expr.span, offset),
429435
};
@@ -468,6 +474,7 @@ const offsetExpr = (expr: SExpr, offset: number): SExpr => {
468474
...expr,
469475
record: offsetExpr(expr.record, offset),
470476
span: offsetSpan(expr.span, offset),
477+
fieldSpan: offsetSpan(expr.fieldSpan, offset),
471478
};
472479
case "SList":
473480
return {
@@ -521,6 +528,7 @@ const offsetDecl = (decl: SDecl, offset: number): SDecl => {
521528
constructors: decl.constructors.map((c) => ({
522529
...c,
523530
fields: c.fields.map((f) => offsetType(f, offset)),
531+
span: offsetSpan(c.span, offset),
524532
})),
525533
span: offsetSpan(decl.span, offset),
526534
};

src/core.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export type CVar = {
4646
readonly kind: "CVar";
4747
readonly name: Name;
4848
readonly span?: Span;
49+
// For qualified names (Module.member), track the spans separately
50+
readonly moduleSpan?: Span;
51+
readonly memberSpan?: Span;
4952
};
5053

5154
export type CLit = {
@@ -67,6 +70,7 @@ export type CAbs = {
6770
readonly param: Name;
6871
readonly body: CExpr;
6972
readonly span?: Span;
73+
readonly paramSpan?: Span;
7074
};
7175

7276
export type CLet = {
@@ -75,11 +79,16 @@ export type CLet = {
7579
readonly value: CExpr;
7680
readonly body: CExpr;
7781
readonly span?: Span;
82+
readonly nameSpan?: Span;
7883
};
7984

8085
export type CLetRec = {
8186
readonly kind: "CLetRec";
82-
readonly bindings: readonly { readonly name: Name; readonly value: CExpr }[];
87+
readonly bindings: readonly {
88+
readonly name: Name;
89+
readonly value: CExpr;
90+
readonly nameSpan?: Span;
91+
}[];
8392
readonly body: CExpr;
8493
readonly span?: Span;
8594
};
@@ -257,6 +266,7 @@ export type CDeclType = {
257266
export type CConDecl = {
258267
readonly name: string;
259268
readonly fields: readonly CType[];
269+
readonly span?: Span;
260270
};
261271

262272
export type CDeclLet = {
@@ -301,29 +311,43 @@ export type CProgram = {
301311
// Smart Constructors
302312
// =============================================================================
303313

304-
export const cvar = (name: Name, span?: Span): CVar => ({ kind: "CVar", name, span });
314+
export const cvar = (name: Name, span?: Span, moduleSpan?: Span, memberSpan?: Span): CVar => ({
315+
kind: "CVar",
316+
name,
317+
span,
318+
moduleSpan,
319+
memberSpan,
320+
});
305321
export const clit = (value: Literal, span?: Span): CLit => ({ kind: "CLit", value, span });
306322
export const capp = (func: CExpr, arg: CExpr, span?: Span): CApp => ({
307323
kind: "CApp",
308324
func,
309325
arg,
310326
span,
311327
});
312-
export const cabs = (param: Name, body: CExpr, span?: Span): CAbs => ({
328+
export const cabs = (param: Name, body: CExpr, span?: Span, paramSpan?: Span): CAbs => ({
313329
kind: "CAbs",
314330
param,
315331
body,
316332
span,
333+
paramSpan,
317334
});
318-
export const clet = (name: Name, value: CExpr, body: CExpr, span?: Span): CLet => ({
335+
export const clet = (
336+
name: Name,
337+
value: CExpr,
338+
body: CExpr,
339+
span?: Span,
340+
nameSpan?: Span,
341+
): CLet => ({
319342
kind: "CLet",
320343
name,
321344
value,
322345
body,
323346
span,
347+
nameSpan,
324348
});
325349
export const cletrec = (
326-
bindings: readonly { name: Name; value: CExpr }[],
350+
bindings: readonly { name: Name; value: CExpr; nameSpan?: Span }[],
327351
body: CExpr,
328352
span?: Span,
329353
): CLetRec => ({ kind: "CLetRec", bindings, body, span });

src/desugar.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const desugarExpr = (expr: S.SExpr): C.CExpr => {
4949
desugarExpr(expr.value),
5050
desugarExpr(expr.body),
5151
expr.span,
52+
expr.nameSpan,
5253
);
5354

5455
case "SLetRec":
@@ -57,6 +58,7 @@ export const desugarExpr = (expr: S.SExpr): C.CExpr => {
5758
expr.bindings.map((b) => ({
5859
name: { id: -1, original: b.name },
5960
value: desugarExpr(b.value),
61+
nameSpan: b.nameSpan,
6062
})),
6163
desugarExpr(expr.body),
6264
expr.span,
@@ -120,7 +122,13 @@ export const desugarExpr = (expr: S.SExpr): C.CExpr => {
120122
if (expr.field[0] && expr.field[0] === expr.field[0].toUpperCase()) {
121123
return C.ccon(qualifiedName, expr.span);
122124
}
123-
return C.cvar({ id: -1, original: qualifiedName }, expr.span);
125+
// Pass module span (from the constructor) and member span (from the field)
126+
return C.cvar(
127+
{ id: -1, original: qualifiedName },
128+
expr.span,
129+
expr.record.span,
130+
expr.fieldSpan,
131+
);
124132
}
125133
// D⟦ SField e f ⟧ = CField (D⟦e⟧) f
126134
return C.cfield(desugarExpr(expr.record), expr.field, expr.span);
@@ -200,15 +208,21 @@ export const desugarExpr = (expr: S.SExpr): C.CExpr => {
200208
// Helper Functions
201209
// =============================================================================
202210

203-
const desugarAbs = (params: readonly string[], body: S.SExpr, span?: S.Span): C.CExpr => {
211+
const desugarAbs = (params: readonly S.SParam[], body: S.SExpr, span?: S.Span): C.CExpr => {
204212
if (params.length === 0) {
205213
return desugarExpr(body);
206214
}
207215

208216
// Build nested lambdas from right to left
209217
let result = desugarExpr(body);
210218
for (let i = params.length - 1; i >= 0; i--) {
211-
result = C.cabs({ id: -1, original: params[i]! }, result, i === 0 ? span : undefined);
219+
const param = params[i]!;
220+
result = C.cabs(
221+
{ id: -1, original: param.name },
222+
result,
223+
i === 0 ? span : undefined,
224+
param.span,
225+
);
212226
}
213227
return result;
214228
};
@@ -535,6 +549,7 @@ const desugarDecl = (decl: S.SDecl): C.CDecl | C.CDecl[] | null => {
535549
decl.constructors.map((c) => ({
536550
name: c.name,
537551
fields: c.fields.map(desugarType),
552+
span: c.span,
538553
})),
539554
decl.span,
540555
);
@@ -728,6 +743,7 @@ const desugarExprInModuleWithImports = (
728743
shadowedImportedNames,
729744
),
730745
expr.span,
746+
expr.nameSpan,
731747
);
732748
}
733749

@@ -745,6 +761,7 @@ const desugarExprInModuleWithImports = (
745761
expr.bindings.map((b) => ({
746762
name: { id: -1, original: b.name },
747763
value: recurseWithShadow(b.value),
764+
nameSpan: b.nameSpan,
748765
})),
749766
recurseWithShadow(expr.body),
750767
expr.span,
@@ -839,7 +856,13 @@ const desugarExprInModuleWithImports = (
839856
if (expr.field[0] && expr.field[0] === expr.field[0].toUpperCase()) {
840857
return C.ccon(qualifiedName, expr.span);
841858
}
842-
return C.cvar({ id: -1, original: qualifiedName }, expr.span);
859+
// Pass module span (from the constructor) and member span (from the field)
860+
return C.cvar(
861+
{ id: -1, original: qualifiedName },
862+
expr.span,
863+
expr.record.span,
864+
expr.fieldSpan,
865+
);
843866
}
844867
return C.cfield(recurse(expr.record), expr.field, expr.span);
845868

@@ -913,7 +936,7 @@ const desugarExprInModuleWithImports = (
913936
};
914937

915938
const desugarAbsInModuleWithImports = (
916-
params: readonly string[],
939+
params: readonly S.SParam[],
917940
body: S.SExpr,
918941
moduleName: string,
919942
moduleNames: Set<string>,
@@ -928,8 +951,8 @@ const desugarAbsInModuleWithImports = (
928951
const shadowedModuleNames = new Set(moduleNames);
929952
const shadowedImportedNames = new Map(importedNames);
930953
for (const param of params) {
931-
shadowedModuleNames.delete(param);
932-
shadowedImportedNames.delete(param);
954+
shadowedModuleNames.delete(param.name);
955+
shadowedImportedNames.delete(param.name);
933956
}
934957

935958
let result = desugarExprInModuleWithImports(
@@ -939,7 +962,13 @@ const desugarAbsInModuleWithImports = (
939962
shadowedImportedNames,
940963
);
941964
for (let i = params.length - 1; i >= 0; i--) {
942-
result = C.cabs({ id: -1, original: params[i]! }, result, i === 0 ? span : undefined);
965+
const param = params[i]!;
966+
result = C.cabs(
967+
{ id: -1, original: param.name },
968+
result,
969+
i === 0 ? span : undefined,
970+
param.span,
971+
);
943972
}
944973
return result;
945974
};
@@ -1133,6 +1162,7 @@ const desugarDeclInModuleWithImports = (
11331162
decl.constructors.map((c) => ({
11341163
name: `${moduleName}.${c.name}`,
11351164
fields: c.fields.map(desugarType),
1165+
span: c.span,
11361166
})),
11371167
decl.span,
11381168
);

0 commit comments

Comments
 (0)