diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c7f74a62..e872e6d524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Better error message for when trying to await something that is not a promise. https://github.com/rescript-lang/rescript/pull/7561 - Better error messages for object field missing and object field type mismatches. https://github.com/rescript-lang/rescript/pull/7580 - Better error messages for when polymorphic variants does not match for various reasons. https://github.com/rescript-lang/rescript/pull/7596 +- Improved completions for inline records. https://github.com/rescript-lang/rescript/pull/7601 #### :house: Internal diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index f678d9a6c0..b051f6be0c 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1443,6 +1443,48 @@ let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens let emptyCase = emptyCase ~mode in let printConstructorArgs = printConstructorArgs ~mode in let create = Completion.create ?typeArgContext in + let getRecordCompletions ~env ~fields ~extractedType = + (* As we're completing for a record, we'll need a hint (completionContext) + here to figure out whether we should complete for a record field, or + the record body itself. *) + match completionContext with + | Some (Completable.RecordField {seenFields}) -> + fields + |> List.filter (fun (field : field) -> + List.mem field.fname.txt seenFields = false) + |> List.map (fun (field : field) -> + match (field.optional, mode) with + | true, Pattern Destructuring -> + create ("?" ^ field.fname.txt) ?deprecated:field.deprecated + ~docstring: + [ + field.fname.txt + ^ " is an optional field, and needs to be destructured \ + using '?'."; + ] + ~kind: + (Field (field, TypeUtils.extractedTypeToString extractedType)) + ~env + | _ -> + create field.fname.txt ?deprecated:field.deprecated + ~kind: + (Field (field, TypeUtils.extractedTypeToString extractedType)) + ~env) + |> filterItems ~prefix + | _ -> + if prefix = "" then + [ + create "{}" ~includesSnippets:true ~insertText:"{$0}" ~sortText:"A" + ~kind: + (ExtractedType + ( extractedType, + match mode with + | Pattern _ -> `Type + | Expression -> `Value )) + ~env; + ] + else [] + in match t with | TtypeT {env; path} when mode = Expression -> if Debug.verbose () then @@ -1710,67 +1752,13 @@ let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens ~insertText:(printConstructorArgs numExprs ~asSnippet:true) ~kind:(Value typ) ~env; ] - | Trecord {env; fields} as extractedType -> ( + | Trecord {env; fields} as extractedType -> if Debug.verbose () then print_endline "[complete_typed_value]--> Trecord"; - (* As we're completing for a record, we'll need a hint (completionContext) - here to figure out whether we should complete for a record field, or - the record body itself. *) - match completionContext with - | Some (Completable.RecordField {seenFields}) -> - fields - |> List.filter (fun (field : field) -> - List.mem field.fname.txt seenFields = false) - |> List.map (fun (field : field) -> - match (field.optional, mode) with - | true, Pattern Destructuring -> - create ("?" ^ field.fname.txt) ?deprecated:field.deprecated - ~docstring: - [ - field.fname.txt - ^ " is an optional field, and needs to be destructured \ - using '?'."; - ] - ~kind: - (Field (field, TypeUtils.extractedTypeToString extractedType)) - ~env - | _ -> - create field.fname.txt ?deprecated:field.deprecated - ~kind: - (Field (field, TypeUtils.extractedTypeToString extractedType)) - ~env) - |> filterItems ~prefix - | _ -> - if prefix = "" then - [ - create "{}" ~includesSnippets:true ~insertText:"{$0}" ~sortText:"A" - ~kind: - (ExtractedType - ( extractedType, - match mode with - | Pattern _ -> `Type - | Expression -> `Value )) - ~env; - ] - else []) - | TinlineRecord {env; fields} -> ( + getRecordCompletions ~env ~fields ~extractedType + | TinlineRecord {env; fields} as extractedType -> if Debug.verbose () then print_endline "[complete_typed_value]--> TinlineRecord"; - match completionContext with - | Some (Completable.RecordField {seenFields}) -> - fields - |> List.filter (fun (field : field) -> - List.mem field.fname.txt seenFields = false) - |> List.map (fun (field : field) -> - create field.fname.txt ~kind:(Label "Inline record") - ?deprecated:field.deprecated ~env) - |> filterItems ~prefix - | _ -> - if prefix = "" then - [ - create "{}" ~includesSnippets:true ~insertText:"{$0}" ~sortText:"A" - ~kind:(Label "Inline record") ~env; - ] - else []) + getRecordCompletions ~env ~fields ~extractedType | Tarray (env, typ) -> if Debug.verbose () then print_endline "[complete_typed_value]--> Tarray"; if prefix = "" then diff --git a/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt b/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt index 585f53f395..6ea8bab362 100644 --- a/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt +++ b/tests/analysis_tests/tests-incremental-typechecking/src/expected/ConstructorCompletion__Own.res.txt @@ -8,10 +8,10 @@ Resolved opens 1 Stdlib ContextPath CTypeAtPos() [{ "label": "{}", - "kind": 4, + "kind": 12, "tags": [], - "detail": "Inline record", - "documentation": null, + "detail": "{miss: bool}", + "documentation": {"kind": "markdown", "value": "```rescript\n{miss: bool}\n```"}, "sortText": "A", "insertText": "{$0}", "insertTextFormat": 2 diff --git a/tests/analysis_tests/tests/src/Completion.res b/tests/analysis_tests/tests/src/Completion.res index 9d8c870769..ce4ca160b7 100644 --- a/tests/analysis_tests/tests/src/Completion.res +++ b/tests/analysis_tests/tests/src/Completion.res @@ -465,3 +465,20 @@ type withUncurried = {fn: int => unit} // let someRecord = { FAR. } // ^com + +type someRecord = {field1: string, field2: int} +type someVariantWithRecord = HasRecord(someRecord) + +// let v: someVariantWithRecord = HasRecord({}) +// ^com + +// let v: someVariantWithRecord = HasRecord({fie}) +// ^com + +type someVariantWithInlineRecord = HasInlineRecord({field1: string, field2: int}) + +// let v: someVariantWithInlineRecord = HasInlineRecord({}) +// ^com + +// let v: someVariantWithInlineRecord = HasInlineRecord({fie}) +// ^com diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index f26d64ee76..18d9163214 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -2342,8 +2342,8 @@ Path red }] Complete src/Completion.res 407:22 -posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:11->468:0] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[407:11->425:17], ...[430:0->468:0]) +posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:11->485:0] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[407:11->425:17], ...[430:0->485:0]) posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:11->425:17] Pexp_apply ...__ghost__[0:-1->0:-1] (...[407:11->407:19], ...[407:21->425:17]) posCursor:[407:22] posNoWhite:[407:21] Found expr:[407:21->425:17] @@ -2747,3 +2747,91 @@ Path FAR. "documentation": {"kind": "markdown", "value": "```rescript\nsomething: option\n```\n\n```rescript\ntype forAutoRecord = {\n forAuto: ForAuto.t,\n something: option,\n}\n```"} }] +Complete src/Completion.res 471:45 +XXX Not found! +Completable: Cexpression Type[someVariantWithRecord]->variantPayload::HasRecord($0), recordBody +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Package opens Stdlib.place holder Pervasives.JsxModules.place holder +Resolved opens 3 Stdlib Completion Completion +ContextPath Type[someVariantWithRecord] +Path someVariantWithRecord +[{ + "label": "field1", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nfield1: string\n```\n\n```rescript\ntype someRecord = {field1: string, field2: int}\n```"} + }, { + "label": "field2", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\nfield2: int\n```\n\n```rescript\ntype someRecord = {field1: string, field2: int}\n```"} + }] + +Complete src/Completion.res 474:48 +XXX Not found! +Completable: Cexpression Type[someVariantWithRecord]=fie->variantPayload::HasRecord($0), recordBody +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Package opens Stdlib.place holder Pervasives.JsxModules.place holder +Resolved opens 3 Stdlib Completion Completion +ContextPath Type[someVariantWithRecord] +Path someVariantWithRecord +[{ + "label": "field1", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nfield1: string\n```\n\n```rescript\ntype someRecord = {field1: string, field2: int}\n```"} + }, { + "label": "field2", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\nfield2: int\n```\n\n```rescript\ntype someRecord = {field1: string, field2: int}\n```"} + }] + +Complete src/Completion.res 479:57 +XXX Not found! +Completable: Cexpression Type[someVariantWithInlineRecord]->variantPayload::HasInlineRecord($0), recordBody +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Package opens Stdlib.place holder Pervasives.JsxModules.place holder +Resolved opens 3 Stdlib Completion Completion +ContextPath Type[someVariantWithInlineRecord] +Path someVariantWithInlineRecord +[{ + "label": "field1", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nfield1: string\n```\n\n```rescript\n{field1: string, field2: int}\n```"} + }, { + "label": "field2", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\nfield2: int\n```\n\n```rescript\n{field1: string, field2: int}\n```"} + }] + +Complete src/Completion.res 482:60 +XXX Not found! +Completable: Cexpression Type[someVariantWithInlineRecord]=fie->variantPayload::HasInlineRecord($0), recordBody +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Package opens Stdlib.place holder Pervasives.JsxModules.place holder +Resolved opens 3 Stdlib Completion Completion +ContextPath Type[someVariantWithInlineRecord] +Path someVariantWithInlineRecord +[{ + "label": "field1", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nfield1: string\n```\n\n```rescript\n{field1: string, field2: int}\n```"} + }, { + "label": "field2", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\nfield2: int\n```\n\n```rescript\n{field1: string, field2: int}\n```"} + }] + diff --git a/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt b/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt index bfea51f5fc..663e00678c 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionExpressions.res.txt @@ -700,10 +700,10 @@ ContextPath Value[fnTakingInlineRecord] Path fnTakingInlineRecord [{ "label": "{}", - "kind": 4, + "kind": 12, "tags": [], - "detail": "Inline record", - "documentation": null, + "detail": "{someBoolField: bool, otherField: option, nestedRecord: otherRecord}", + "documentation": {"kind": "markdown", "value": "```rescript\n{someBoolField: bool, otherField: option, nestedRecord: otherRecord}\n```"}, "sortText": "A", "insertText": "{$0}", "insertTextFormat": 2 @@ -720,22 +720,22 @@ ContextPath Value[fnTakingInlineRecord] Path fnTakingInlineRecord [{ "label": "someBoolField", - "kind": 4, + "kind": 5, "tags": [], - "detail": "Inline record", - "documentation": null + "detail": "bool", + "documentation": {"kind": "markdown", "value": "```rescript\nsomeBoolField: bool\n```\n\n```rescript\n{someBoolField: bool, otherField: option, nestedRecord: otherRecord}\n```"} }, { "label": "otherField", - "kind": 4, + "kind": 5, "tags": [], - "detail": "Inline record", - "documentation": null + "detail": "option", + "documentation": {"kind": "markdown", "value": "```rescript\notherField: option\n```\n\n```rescript\n{someBoolField: bool, otherField: option, nestedRecord: otherRecord}\n```"} }, { "label": "nestedRecord", - "kind": 4, + "kind": 5, "tags": [], - "detail": "Inline record", - "documentation": null + "detail": "otherRecord", + "documentation": {"kind": "markdown", "value": "```rescript\nnestedRecord: otherRecord\n```\n\n```rescript\n{someBoolField: bool, otherField: option, nestedRecord: otherRecord}\n```"} }] Complete src/CompletionExpressions.res 132:51 @@ -749,10 +749,10 @@ ContextPath Value[fnTakingInlineRecord] Path fnTakingInlineRecord [{ "label": "someBoolField", - "kind": 4, + "kind": 5, "tags": [], - "detail": "Inline record", - "documentation": null + "detail": "bool", + "documentation": {"kind": "markdown", "value": "```rescript\nsomeBoolField: bool\n```\n\n```rescript\n{someBoolField: bool, otherField: option, nestedRecord: otherRecord}\n```"} }] Complete src/CompletionExpressions.res 135:63