Skip to content

Commit 417ad95

Browse files
cknittclaude
andcommitted
Resolve an open's extension constructor only through its module
Why: Codex review on #9085: an `open A` after a local extension `Value` resolved the payload of an unqualified `Value(...)` to the local constructor. Matching `A.Value` against the constructors also accepted the bare local `Value` as a suffix, and the later declaration won. A constructor named without a module is declared around the cursor, so an open no longer brings it into scope. Completion modified (extension payload after an open): Example: type attr = .. module A = { type attr += Value(bool) } type attr += Value(option<int>) open A let x: attr = Value() Before (text): payload completion inside `Value()` completes option<int>, the payload of the local Value After (text): payload completion inside `Value()` completes bool, the payload of A.Value: true, false Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kr4cjoUi2f5VpMvE6wT9bo Signed-off-by: Christoph Knittel <ck@cca.io>
1 parent 30f0ed2 commit 417ad95

3 files changed

Lines changed: 42 additions & 1 deletion

File tree

‎analysis/src/type_utils.ml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,10 @@ let find_payload_constructor ~(env : Query_env.t) ~constructors ~extension_envs
426426
let opened =
427427
List.filteri (fun i _ -> i < List.length opened - 1) opened
428428
in
429-
match last (named (opened @ path)) with
429+
(* A constructor named without a module path is declared around
430+
the cursor, so no open brings it into scope. *)
431+
let in_module (c : Constructor.t) = List.length (parts c) > 1 in
432+
match last (named (opened @ path) |> List.filter in_module) with
430433
| Some c -> Some c
431434
| None -> lookup scope)
432435
| _ :: scope -> lookup scope

‎tests/analysis_tests/tests/src/CompletionExtensiblePayload.res‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,12 @@ module type S = {
120120

121121
// switch v
122122
// ^com
123+
124+
// An open after a local declaration shadows it in turn
125+
open A
126+
127+
// let x: attr = Value()
128+
// ^com
129+
130+
// switch v { | Value() }
131+
// ^com

‎tests/analysis_tests/tests/src/expected/CompletionExtensiblePayload.res.txt‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,3 +1254,32 @@ Path v
12541254
}
12551255
]
12561256

1257+
Complete src/CompletionExtensiblePayload.res 126:23
1258+
XXX Not found!
1259+
Completable: Cexpression Type[attr]->variantPayload::Value($0)
1260+
Raw opens: 3 A.place holder ... B.place holder ... A.place holder
1261+
Package opens Stdlib.place holder Pervasives.JsxModules.place holder
1262+
Resolved opens 4 Stdlib CompletionExtensiblePayload CompletionExtensiblePayload CompletionExtensiblePayload
1263+
ContextPath Type[attr]
1264+
Path attr
1265+
[
1266+
{ "detail": "bool", "kind": 4, "label": "true", "tags": [] },
1267+
{ "detail": "bool", "kind": 4, "label": "false", "tags": [] }
1268+
]
1269+
1270+
Complete src/CompletionExtensiblePayload.res 129:22
1271+
pos_cursor:[129:22] pos_no_white:[129:21] Found pattern:[129:16->129:23]
1272+
Ppat_construct Value:[129:16->129:21]
1273+
pos_cursor:[129:22] pos_no_white:[129:21] Found pattern:[129:21->129:23]
1274+
Ppat_construct ():[129:21->129:23]
1275+
Completable: Cpattern Value[v]->variantPayload::Value($0)
1276+
Raw opens: 3 A.place holder ... B.place holder ... A.place holder
1277+
Package opens Stdlib.place holder Pervasives.JsxModules.place holder
1278+
Resolved opens 4 Stdlib CompletionExtensiblePayload CompletionExtensiblePayload CompletionExtensiblePayload
1279+
ContextPath Value[v]
1280+
Path v
1281+
[
1282+
{ "detail": "bool", "kind": 4, "label": "true", "tags": [] },
1283+
{ "detail": "bool", "kind": 4, "label": "false", "tags": [] }
1284+
]
1285+

0 commit comments

Comments
 (0)