Repository: edgedb/edgedb

Issue:
User-defined functions with variadic+optional arguments have problems
Variadic functions have all their variadic arguments packed into an array. If one of those arguments is `NULL`, the array will have a `NULL` in it, which is not legit in edgeql.

```
edgedb> create function foo(variadic doc: optional int64) -> array<int64> using (assert_exists(doc));
OK: CREATE FUNCTION
edgedb> select foo(1, <int64>{}, 3);
[[1, null, 3]]
edgedb> \set output-format default
edgedb> select foo(1, <int64>{}, 3);
edgedb error: ProtocolEncodingError: ProtocolEncodingError: unexpected end of frame
Reconnecting...
```

I'm not sure that there is any sensible way to handle this in edgeql as it exists now, so we should probably just ban this.


Discussion:
Yes, variadic+optional should be banned

Resolving patch:
diff --git a/edb/schema/functions.py b/edb/schema/functions.py
--- a/edb/schema/functions.py
+++ b/edb/schema/functions.py
@@ -1879,6 +1879,20 @@ def _create_begin(
                         f'{p_type.get_displayname(schema)}',
                         context=self.source_context)
 
+        # Make sure variadic parameters do not contain optional types in
+        # user-defined functions
+        if language == qlast.Language.EdgeQL:
+            if variadic := params.find_variadic(schema):
+                typemod = variadic.get_typemod(schema)
+                if typemod is ft.TypeModifier.OptionalType:
+                    raise errors.InvalidFunctionDefinitionError(
+                        f'cannot create the `{signature}` function: '
+                        f'variadic argument '
+                        f'`{variadic.get_displayname(schema)}` '
+                        f'illegally declared with optional type in '
+                        f'user-defined function',
+                        context=self.source_context)
+
         return schema
 
     @classmethod


Test patch:
diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py
--- a/tests/test_edgeql_ddl.py
+++ b/tests/test_edgeql_ddl.py
@@ -4763,6 +4763,20 @@ async def test_edgeql_ddl_function_35(self):
                 $$;
             """)
 
+    async def test_edgeql_ddl_function_36(self):
+        with self.assertRaisesRegex(
+            edgedb.InvalidFunctionDefinitionError,
+            r"cannot create the `default::broken_edgeql_func36\("
+            r"VARIADIC foo: OPTIONAL array<std::int64>\)` function: "
+            r"variadic argument `foo` illegally declared with "
+            r"optional type in user-defined function"
+        ):
+            await self.con.execute(r"""
+                CREATE FUNCTION broken_edgeql_func36(
+                    variadic foo: optional std::int64) -> array<std::int64>
+                USING (assert_exists(foo));
+            """)
+
     async def test_edgeql_ddl_function_rename_01(self):
         await self.con.execute("""
             CREATE FUNCTION foo(s: str) -> str {
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -793,6 +793,19 @@ def test_schema_module_reserved_02(self):
             }
         """
 
+    @tb.must_fail(
+        errors.InvalidFunctionDefinitionError,
+        r"cannot create the `test::foo\(VARIADIC bar: "
+        r"OPTIONAL array<std::int64>\)` function: "
+        r"variadic argument `bar` illegally declared "
+        r"with optional type in user-defined function"
+    )
+    def test_schema_func_optional_variadic_01(self):
+        """
+            function foo(variadic bar: optional int64) -> array<int64>
+                using (assert_exists(bar));
+        """
+
     def test_schema_refs_01(self):
         schema = self.load_schema("""
             type Object1;