Repository: edgedb/edgedb

Issue:
Improve error message on abstract property with default value
A user [encountered this on Discord](https://discord.com/channels/841451783728529451/849377751370432573/1054704821560684564) and assumed abstract properties were bugged. Adding a default value to an abstract property appears to be prohibited, but the error message does not indicate the problem:

```
edgedb error: InternalServerError: no referrer context for <class 'edb.schema.properties.AlterProperty'>
```

Can a more specific error be returned in this case to reflect that abstract properties do not support default values (or maybe that they only support `readonly` and annotations)?


Discussion:
`InternalServerError` is always a bug.
@elprans Do you mean that `default` should work in this context, or is the bug that the error message isn't specific?
Either would be a valid fix here.  As for which exactly I defer to @msullivan and @aljazerzen.

Resolving patch:
diff --git a/edb/schema/links.py b/edb/schema/links.py
--- a/edb/schema/links.py
+++ b/edb/schema/links.py
@@ -333,12 +333,6 @@ def _cmd_tree_from_ast(
         if isinstance(astnode, qlast.CreateConcreteLink):
             assert isinstance(cmd, pointers.PointerCommand)
             cmd._process_create_or_alter_ast(schema, astnode, context)
-        else:
-            # this is an abstract property then
-            if cmd.get_attribute_value('default') is not None:
-                raise errors.SchemaDefinitionError(
-                    f"'default' is not a valid field for an abstract link",
-                    context=astnode.context)
         assert isinstance(cmd, sd.Command)
         return cmd
 
diff --git a/edb/schema/pointers.py b/edb/schema/pointers.py
--- a/edb/schema/pointers.py
+++ b/edb/schema/pointers.py
@@ -1404,14 +1404,16 @@ def compile_expr_field(
         value: s_expr.Expression,
         track_schema_ref_exprs: bool=False,
     ) -> s_expr.CompiledExpression:
+
         if field.name in {'default', 'expr'}:
-            parent_ctx = self.get_referrer_context_or_die(context)
-            source = parent_ctx.op.get_object(schema, context)
-            parent_vname = source.get_verbosename(schema)
-            ptr_name = self.get_verbosename(parent=parent_vname)
-            in_ddl_context_name = None
             if field.name == 'expr':
+                parent_ctx = self.get_referrer_context_or_die(context)
+                source = parent_ctx.op.get_object(schema, context)
+                parent_vname = source.get_verbosename(schema)
+                ptr_name = self.get_verbosename(parent=parent_vname)
                 in_ddl_context_name = f'computed {ptr_name}'
+            else:
+                in_ddl_context_name = None
 
             return self._compile_expr(
                 schema,
@@ -1717,10 +1719,19 @@ def _cmd_tree_from_ast(
         context: sd.CommandContext,
     ) -> sd.Command:
         cmd = super()._cmd_tree_from_ast(schema, astnode, context)
+        assert isinstance(cmd, PointerCommand)
+
         referrer_ctx = cls.get_referrer_context(context)
         if referrer_ctx is not None:
             if getattr(astnode, 'declared_overloaded', False):
                 cmd.set_attribute_value('declared_overloaded', True)
+        else:
+            # This is an abstract property/link
+            if cmd.get_attribute_value('default') is not None:
+                typ = cls.get_schema_metaclass().get_schema_class_displayname()
+                raise errors.SchemaDefinitionError(
+                    f"'default' is not a valid field for an abstract {typ}",
+                    context=astnode.context)
         return cmd
 
     def _process_create_or_alter_ast(
diff --git a/edb/schema/properties.py b/edb/schema/properties.py
--- a/edb/schema/properties.py
+++ b/edb/schema/properties.py
@@ -292,12 +292,6 @@ def _cmd_tree_from_ast(
         if isinstance(astnode, qlast.CreateConcreteProperty):
             assert isinstance(cmd, pointers.PointerCommand)
             cmd._process_create_or_alter_ast(schema, astnode, context)
-        else:
-            # this is an abstract property then
-            if cmd.get_attribute_value('default') is not None:
-                raise errors.SchemaDefinitionError(
-                    f"'default' is not a valid field for an abstract property",
-                    context=astnode.context)
 
         return cmd
 


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
@@ -3495,6 +3495,17 @@ async def test_edgeql_ddl_link_bad_03(self):
                     };
                 """)
 
+    async def test_edgeql_ddl_link_bad_04(self):
+        with self.assertRaisesRegex(
+                edgedb.SchemaDefinitionError,
+                f"'default' is not a valid field for an abstract link"):
+            async with self.con.transaction():
+                await self.migrate("""
+                    abstract link bar {
+                        default := Object;
+                    };
+                """)
+
     async def test_edgeql_ddl_property_long_01(self):
         prop_name = (
             'f123456789_123456789_123456789_123456789'
@@ -3534,6 +3545,17 @@ async def test_edgeql_ddl_property_bad_03(self):
                     };
                 """)
 
+    async def test_edgeql_ddl_property_bad_04(self):
+        with self.assertRaisesRegex(
+                edgedb.SchemaDefinitionError,
+                f"'default' is not a valid field for an abstract property"):
+            async with self.con.transaction():
+                await self.migrate("""
+                    abstract property currency_fallback {
+                        default := 'EUR';
+                    };
+                """)
+
     async def test_edgeql_ddl_function_01(self):
         await self.con.execute("""
             CREATE FUNCTION my_lower(s: std::str) -> std::str