Skip to content

Commit

Permalink
Add hint when calling a type or casting to function. (#7154)
Browse files Browse the repository at this point in the history
Provides hints for queries such as `select <to_str>(1)` or `select str(1)`.
  • Loading branch information
dnwpark authored Apr 4, 2024
1 parent 4b3dca2 commit 6740474
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 4 deletions.
21 changes: 20 additions & 1 deletion edb/edgeql/compiler/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
20 changes: 19 additions & 1 deletion edb/edgeql/compiler/func.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
5 changes: 3 additions & 2 deletions edb/schema/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)

Expand Down
28 changes: 28 additions & 0 deletions tests/test_edgeql_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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;
""")
28 changes: 28 additions & 0 deletions tests/test_edgeql_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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);
""")

0 comments on commit 6740474

Please sign in to comment.