From 477d8fad3caece3dc2600768d2c5365bef7ca480 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 16 Aug 2025 11:19:17 +0200 Subject: [PATCH 1/2] simple %typeof poc --- compiler/ml/typetexp.ml | 22 +++++++++++++++++-- tests/analysis_tests/tests/src/Hover.res | 5 +++++ .../tests/src/expected/Hover.res.txt | 3 +++ .../expected/typeof_mismatch.res.expected | 12 ++++++++++ .../super_errors/fixtures/typeof_mismatch.res | 6 +++++ tests/tests/src/Typeof.res | 5 +++++ 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/typeof_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/typeof_mismatch.res create mode 100644 tests/tests/src/Typeof.res diff --git a/compiler/ml/typetexp.ml b/compiler/ml/typetexp.ml index 53758e26c1..53f8d1139c 100644 --- a/compiler/ml/typetexp.ml +++ b/compiler/ml/typetexp.ml @@ -565,8 +565,26 @@ and transl_type_aux env policy styp = pack_txt = p; }) ty - | Ptyp_extension ext -> - raise (Error_forward (Builtin_attributes.error_of_extension ext)) + | Ptyp_extension ext -> ( + match ext with + | ({txt = "typeof"; loc = ext_loc}, payload) -> ( + (* %typeof payload must be a single identifier *) + match Ast_payload.as_ident payload with + | Some ({txt = lid; loc = lid_loc} as _ident) -> ( + (* Lookup the value and embed a generic instance of its type. + Using a generic instance avoids capturing weak (non-generalized) + type variables from the value into a type position. *) + let (_path, desc) = find_value env lid_loc lid in + let ty = Ctype.generic_instance env desc.val_type in + (* Build a core_type node carrying the looked up type; we mark the + desc as any since downstream only consults ctyp_type for typing. *) + ctyp Ttyp_any ty) + | None -> + let msg = + "%%typeof expects an identifier. Example: type t = %typeof(x)" + in + raise (Error_forward (Location.error ~loc:ext_loc msg))) + | _ -> raise (Error_forward (Builtin_attributes.error_of_extension ext))) and transl_poly_type env policy t = transl_type env policy (Ast_helper.Typ.force_poly t) diff --git a/tests/analysis_tests/tests/src/Hover.res b/tests/analysis_tests/tests/src/Hover.res index bd40d4e712..8eca4d8000 100644 --- a/tests/analysis_tests/tests/src/Hover.res +++ b/tests/analysis_tests/tests/src/Hover.res @@ -281,3 +281,8 @@ module Arr = Belt.Array type aliased = variant // ^hov + +let myFn = (a, b) => a ++ b->Int.toString + +type fnType = %typeof(myFn) +// ^hov \ No newline at end of file diff --git a/tests/analysis_tests/tests/src/expected/Hover.res.txt b/tests/analysis_tests/tests/src/expected/Hover.res.txt index a68c232167..f190ecd61f 100644 --- a/tests/analysis_tests/tests/src/expected/Hover.res.txt +++ b/tests/analysis_tests/tests/src/expected/Hover.res.txt @@ -348,3 +348,6 @@ Hover src/Hover.res 278:8 Hover src/Hover.res 281:6 {"contents": {"kind": "markdown", "value": "```rescript\ntype aliased = variant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C251%2C0%5D)\n"}} +Hover src/Hover.res 286:6 +{"contents": {"kind": "markdown", "value": "```rescript\ntype fnType = (string, int) => string\n```"}} + diff --git a/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected b/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected new file mode 100644 index 0000000000..9372003553 --- /dev/null +++ b/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected @@ -0,0 +1,12 @@ + + We've found a bug for you! + /.../fixtures/typeof_mismatch.res:6:28 + + 4 │ + 5 │ let f: fnType = myFn + 6 │ let ff: fnType = (a, b) => a->Int.toString + b + + This has type: string + But this function argument is expecting: int + + You can convert string to int with Int.fromString. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/typeof_mismatch.res b/tests/build_tests/super_errors/fixtures/typeof_mismatch.res new file mode 100644 index 0000000000..3fbbd86156 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/typeof_mismatch.res @@ -0,0 +1,6 @@ +let myFn = (a, b) => a ++ b->Int.toString + +type fnType = %typeof(myFn) + +let f: fnType = myFn +let ff: fnType = (a, b) => a->Int.toString + b \ No newline at end of file diff --git a/tests/tests/src/Typeof.res b/tests/tests/src/Typeof.res new file mode 100644 index 0000000000..b78bcbaf25 --- /dev/null +++ b/tests/tests/src/Typeof.res @@ -0,0 +1,5 @@ +let myFn = (a, b) => a ++ b->Int.toString + +type fnType = %typeof(myFn) + +let f: fnType = myFn \ No newline at end of file From 1b968b7927815350d60cb724e6316ac3dd621e90 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 19 Aug 2025 21:13:11 +0200 Subject: [PATCH 2/2] add partial application typeof test --- tests/analysis_tests/tests/src/Hover.res | 14 +++-- .../tests/src/expected/Hover.res.txt | 51 ++++++++++--------- .../expected/typeof_mismatch.res.expected | 1 + .../super_errors/fixtures/typeof_mismatch.res | 2 +- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/tests/analysis_tests/tests/src/Hover.res b/tests/analysis_tests/tests/src/Hover.res index 8eca4d8000..f1174bc69c 100644 --- a/tests/analysis_tests/tests/src/Hover.res +++ b/tests/analysis_tests/tests/src/Hover.res @@ -211,7 +211,6 @@ let usr: useR = { // let f = usr // ^hov - module NotShadowed = { /** Stuff */ let xx_ = 10 @@ -249,7 +248,11 @@ let x: recordWithDocstringField = { let someField = x.someField // ^hov -type variant = | /** Cool variant! */ CoolVariant | /** Other cool variant */ OtherCoolVariant +type variant = + /** Cool variant! */ + | CoolVariant + /** Other cool variant */ + | OtherCoolVariant let coolVariant = CoolVariant // ^hov @@ -285,4 +288,9 @@ type aliased = variant let myFn = (a, b) => a ++ b->Int.toString type fnType = %typeof(myFn) -// ^hov \ No newline at end of file +// ^hov + +let myFnPartial = myFn("hello", ...) + +type fnTypePartial = %typeof(myFnPartial) +// ^hov diff --git a/tests/analysis_tests/tests/src/expected/Hover.res.txt b/tests/analysis_tests/tests/src/expected/Hover.res.txt index f190ecd61f..ab0b55f80e 100644 --- a/tests/analysis_tests/tests/src/expected/Hover.res.txt +++ b/tests/analysis_tests/tests/src/expected/Hover.res.txt @@ -271,16 +271,16 @@ Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib {"contents": {"kind": "markdown", "value": "```rescript\nuseR\n```\n\n---\n\n```\n \n```\n```rescript\ntype useR = {x: int, y: list>>}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C200%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype r<'a> = {i: 'a, f: float}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C101%2C0%5D)\n"}} -Hover src/Hover.res 230:20 +Hover src/Hover.res 229:20 {"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n More Stuff "}} -Hover src/Hover.res 233:17 +Hover src/Hover.res 232:17 {"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n---\n More Stuff "}} -Hover src/Hover.res 245:6 +Hover src/Hover.res 244:6 Nothing at that position. Now trying to use completion. -posCursor:[245:6] posNoWhite:[245:5] Found expr:[245:3->245:14] -Pexp_field [245:3->245:4] someField:[245:5->245:14] +posCursor:[244:6] posNoWhite:[244:5] Found expr:[244:3->244:14] +Pexp_field [244:3->244:4] someField:[244:5->244:14] Completable: Cpath Value[x].someField Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib @@ -297,25 +297,25 @@ Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib {"contents": {"kind": "markdown", "value": " Mighty fine field here. \n\n```rescript\nbool\n```"}} -Hover src/Hover.res 248:19 +Hover src/Hover.res 247:19 {"contents": {"kind": "markdown", "value": "```rescript\nbool\n```\n---\n Mighty fine field here. "}} -Hover src/Hover.res 253:20 -{"contents": {"kind": "markdown", "value": "```rescript\nvariant\nCoolVariant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C251%2C0%5D)\n\n---\n Cool variant! "}} +Hover src/Hover.res 256:20 +{"contents": {"kind": "markdown", "value": "```rescript\nvariant\nCoolVariant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C250%2C0%5D)\n\n---\n Cool variant! "}} -Hover src/Hover.res 258:22 -{"contents": {"kind": "markdown", "value": "```rescript\npayloadVariants\nInlineRecord({field1: int, field2: bool})\n```\n\n---\n\n```\n \n```\n```rescript\ntype payloadVariants =\n | InlineRecord({field1: int, field2: bool})\n | Args(int, bool)\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C256%2C0%5D)\n"}} +Hover src/Hover.res 261:22 +{"contents": {"kind": "markdown", "value": "```rescript\npayloadVariants\nInlineRecord({field1: int, field2: bool})\n```\n\n---\n\n```\n \n```\n```rescript\ntype payloadVariants =\n | InlineRecord({field1: int, field2: bool})\n | Args(int, bool)\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C259%2C0%5D)\n"}} -Hover src/Hover.res 261:23 -{"contents": {"kind": "markdown", "value": "```rescript\npayloadVariants\nArgs(int, bool)\n```\n\n---\n\n```\n \n```\n```rescript\ntype payloadVariants =\n | InlineRecord({field1: int, field2: bool})\n | Args(int, bool)\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C256%2C0%5D)\n"}} +Hover src/Hover.res 264:23 +{"contents": {"kind": "markdown", "value": "```rescript\npayloadVariants\nArgs(int, bool)\n```\n\n---\n\n```\n \n```\n```rescript\ntype payloadVariants =\n | InlineRecord({field1: int, field2: bool})\n | Args(int, bool)\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C259%2C0%5D)\n"}} -Hover src/Hover.res 268:42 -{"contents": {"kind": "markdown", "value": "```rescript\nRecursiveVariants.t\nAction1(int)\n```\n\n---\n\n```\n \n```\n```rescript\ntype RecursiveVariants.t =\n | Action1(int)\n | Action2(float)\n | Batch(array)\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C265%2C2%5D)\n"}} +Hover src/Hover.res 271:42 +{"contents": {"kind": "markdown", "value": "```rescript\nRecursiveVariants.t\nAction1(int)\n```\n\n---\n\n```\n \n```\n```rescript\ntype RecursiveVariants.t =\n | Action1(int)\n | Action2(float)\n | Batch(array)\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C268%2C2%5D)\n"}} -Hover src/Hover.res 272:23 +Hover src/Hover.res 275:23 Nothing at that position. Now trying to use completion. -posCursor:[272:23] posNoWhite:[272:22] Found expr:[272:22->272:25] -Pexp_ident fff:[272:22->272:25] +posCursor:[275:23] posNoWhite:[275:22] Found expr:[275:22->275:25] +Pexp_ident fff:[275:22->275:25] Completable: Cpath Value[fff] Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib @@ -326,10 +326,10 @@ Resolved opens 1 Stdlib ContextPath string {"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}} -Hover src/Hover.res 275:33 +Hover src/Hover.res 278:33 Nothing at that position. Now trying to use completion. -posCursor:[275:33] posNoWhite:[275:32] Found expr:[275:31->275:40] -Pexp_ident someField:[275:31->275:40] +posCursor:[278:33] posNoWhite:[278:32] Found expr:[278:31->278:40] +Pexp_ident someField:[278:31->278:40] Completable: Cpath Value[someField] Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib @@ -342,12 +342,15 @@ ContextPath Value[x] Path x {"contents": {"kind": "markdown", "value": "```rescript\nbool\n```"}} -Hover src/Hover.res 278:8 +Hover src/Hover.res 281:8 {"contents": {"kind": "markdown", "value": "\n [`Belt.Array`]()\n\n **mutable array**: Utilities functions\n\n```rescript\nmodule Array: {\n module Id\n module Array\n module SortArray\n module MutableQueue\n module MutableStack\n module List\n module Range\n module Set\n module Map\n module MutableSet\n module MutableMap\n module HashSet\n module HashMap\n module Option\n module Result\n module Int\n module Float\n}\n```"}} -Hover src/Hover.res 281:6 -{"contents": {"kind": "markdown", "value": "```rescript\ntype aliased = variant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C251%2C0%5D)\n"}} +Hover src/Hover.res 284:6 +{"contents": {"kind": "markdown", "value": "```rescript\ntype aliased = variant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C250%2C0%5D)\n"}} -Hover src/Hover.res 286:6 +Hover src/Hover.res 289:6 {"contents": {"kind": "markdown", "value": "```rescript\ntype fnType = (string, int) => string\n```"}} +Hover src/Hover.res 294:6 +{"contents": {"kind": "markdown", "value": "```rescript\ntype fnTypePartial = int => string\n```"}} + diff --git a/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected b/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected index 9372003553..b4c28afc92 100644 --- a/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected +++ b/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected @@ -5,6 +5,7 @@ 4 │ 5 │ let f: fnType = myFn 6 │ let ff: fnType = (a, b) => a->Int.toString + b + 7 │ This has type: string But this function argument is expecting: int diff --git a/tests/build_tests/super_errors/fixtures/typeof_mismatch.res b/tests/build_tests/super_errors/fixtures/typeof_mismatch.res index 3fbbd86156..784ac0b97d 100644 --- a/tests/build_tests/super_errors/fixtures/typeof_mismatch.res +++ b/tests/build_tests/super_errors/fixtures/typeof_mismatch.res @@ -3,4 +3,4 @@ let myFn = (a, b) => a ++ b->Int.toString type fnType = %typeof(myFn) let f: fnType = myFn -let ff: fnType = (a, b) => a->Int.toString + b \ No newline at end of file +let ff: fnType = (a, b) => a->Int.toString + b