Repository: edgedb/edgedb

Issue:
Better suggestions when a function doesn't exist but a type does
```
ERROR: InvalidReferenceError: function 'cal::local_date' does not exist
```

could suggest something in the vein of "Did you mean:".

This is similar but not identical to #915.


Discussion:

Resolving patch:
diff --git a/edb/edgeql/compiler/expr.py b/edb/edgeql/compiler/expr.py
--- a/edb/edgeql/compiler/expr.py
+++ b/edb/edgeql/compiler/expr.py
@@ -35,6 +35,7 @@
 
 from edb.schema import abc as s_abc
 from edb.schema import constraints as s_constr
+from edb.schema import functions as s_func
 from edb.schema import globals as s_globals
 from edb.schema import indexes as s_indexes
 from edb.schema import name as sn
@@ -646,7 +647,25 @@ def compile_GlobalExpr(
 def compile_TypeCast(
     expr: qlast.TypeCast, *, ctx: context.ContextLevel
 ) -> irast.Set:
-    target_stype = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx)
+    try:
+        target_stype = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx)
+    except errors.InvalidReferenceError as e:
+        if (
+            e.hint is None
+            and isinstance(expr.type, qlast.TypeName)
+            and isinstance(expr.type.maintype, qlast.ObjectRef)
+        ):
+            s_utils.enrich_schema_lookup_error(
+                e,
+                s_utils.ast_ref_to_name(expr.type.maintype),
+                modaliases=ctx.modaliases,
+                schema=ctx.env.schema,
+                suggestion_limit=1,
+                item_type=s_func.Function,
+                hint_text='did you mean to call'
+            )
+        raise
+
     ir_expr: Union[irast.Set, irast.Expr]
 
     if isinstance(expr.expr, qlast.Parameter):
diff --git a/edb/edgeql/compiler/func.py b/edb/edgeql/compiler/func.py
--- a/edb/edgeql/compiler/func.py
+++ b/edb/edgeql/compiler/func.py
@@ -53,6 +53,7 @@
 from edb.schema import types as s_types
 from edb.schema import indexes as s_indexes
 from edb.schema import schema as s_schema
+from edb.schema import utils as s_utils
 
 from edb.edgeql import ast as qlast
 from edb.edgeql import qltypes as ft
@@ -95,7 +96,24 @@ def compile_FunctionCall(
     else:
         funcname = sn.QualName(*expr.func)
 
-    funcs = env.schema.get_functions(funcname, module_aliases=ctx.modaliases)
+    try:
+        funcs = env.schema.get_functions(
+            funcname,
+            module_aliases=ctx.modaliases,
+        )
+    except errors.InvalidReferenceError as e:
+        s_utils.enrich_schema_lookup_error(
+            e,
+            funcname,
+            modaliases=ctx.modaliases,
+            schema=env.schema,
+            suggestion_limit=1,
+            item_type=s_types.Type,
+            span=expr.span,
+            hint_text='did you mean to cast to'
+        )
+        raise
+
     prefer_subquery_args = any(
         func.get_prefer_subquery_args(env.schema) for func in funcs
     )
diff --git a/edb/schema/utils.py b/edb/schema/utils.py
--- a/edb/schema/utils.py
+++ b/edb/schema/utils.py
@@ -986,6 +986,7 @@ def enrich_schema_lookup_error(
     condition: Optional[Callable[[so.Object], bool]] = None,
     span: Optional[parsing.Span] = None,
     pointer_parent: Optional[so.Object] = None,
+    hint_text: str = 'did you mean'
 ) -> None:
 
     all_suggestions = itertools.chain(
@@ -1007,9 +1008,9 @@ def enrich_schema_lookup_error(
         names = [name for _, name in suggestions]
 
         if len(names) > 1:
-            hint = f'did you mean one of these: {", ".join(names)}?'
+            hint = f'{hint_text} one of these: {", ".join(names)}?'
         else:
-            hint = f'did you mean {names[0]!r}?'
+            hint = f'{hint_text} {names[0]!r}?'
 
         error.set_hint_and_details(hint=hint)
 


Test patch:
diff --git a/tests/test_edgeql_expressions.py b/tests/test_edgeql_expressions.py
--- a/tests/test_edgeql_expressions.py
+++ b/tests/test_edgeql_expressions.py
@@ -9798,3 +9798,31 @@ async def test_edgeql_overflow_error(self):
             await self.con.query(f'''
                 with x := 1337, select {body}
             ''')
+
+    async def test_edgeql_cast_to_function_01(self):
+        async with self.assertRaisesRegexTx(
+            edgedb.errors.InvalidReferenceError,
+            "does not exist",
+            _hint="did you mean to call 'to_str'?"
+        ):
+            await self.con.execute(f"""
+                select <to_str>1;
+            """)
+
+        async with self.assertRaisesRegexTx(
+            edgedb.errors.InvalidReferenceError,
+            "does not exist",
+            _hint="did you mean to call 'round'?"
+        ):
+            await self.con.execute(f"""
+                select <round>1;
+            """)
+
+        async with self.assertRaisesRegexTx(
+            edgedb.errors.InvalidReferenceError,
+            "does not exist",
+            _hint="did you mean to call 'cal::to_local_date'?"
+        ):
+            await self.con.execute(f"""
+                select <cal::to_local_date>1;
+            """)
diff --git a/tests/test_edgeql_functions.py b/tests/test_edgeql_functions.py
--- a/tests/test_edgeql_functions.py
+++ b/tests/test_edgeql_functions.py
@@ -7914,3 +7914,31 @@ async def test_edgeql_functions_encoding_base64_bad(self):
             await self.con.execute(
                 'select std::enc::base64_decode("AA")'
             )
+
+    async def test_edgeql_call_type_as_function_01(self):
+        async with self.assertRaisesRegexTx(
+            edgedb.errors.InvalidReferenceError,
+            "does not exist",
+            _hint="did you mean to cast to 'str'?",
+        ):
+            await self.con.execute(f"""
+                select str(1);
+            """)
+
+        async with self.assertRaisesRegexTx(
+            edgedb.errors.InvalidReferenceError,
+            "does not exist",
+            _hint="did you mean to cast to 'int32'?",
+        ):
+            await self.con.execute(f"""
+                select int32(1);
+            """)
+
+        async with self.assertRaisesRegexTx(
+            edgedb.errors.InvalidReferenceError,
+            "does not exist",
+            _hint="did you mean to cast to 'cal::local_date'?",
+        ):
+            await self.con.execute(f"""
+                select cal::local_date(1);
+            """)