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..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 @@ -281,3 +284,13 @@ module Arr = Belt.Array type aliased = variant // ^hov + +let myFn = (a, b) => a ++ b->Int.toString + +type fnType = %typeof(myFn) +// ^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 a68c232167..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,9 +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 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 new file mode 100644 index 0000000000..b4c28afc92 --- /dev/null +++ b/tests/build_tests/super_errors/expected/typeof_mismatch.res.expected @@ -0,0 +1,13 @@ + + 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 + 7 │ + + 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..784ac0b97d --- /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 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