Repository: edgedb/edgedb

Issue:
Broken order dependence when deriving types
If you do
```
create type A {
    create property foo := "!";
    create property bar -> str;
};
alter type A alter property foo using (.bar);
```

And then
```
create type B extending A;
```
we get a totally bogus `object type 'default::B' has no link or property 'bar'` error, complete with broken source span.

The cause here is that `inherit_fields` recompiles all expression fields as soon as it sees them, and so if the order that pointers are reprocessed does not match their internal dependency order, then things will break.

It seems that in practice they get processed in the order we get them back from the introspection tables, which in practice seems to be the order that they were created. So that's why most normal cases work, and this weird case that adds a dependency after the fact breaks. But as far as I can tell even the working cases work by accident, since we don't do anything to ensure the ordering


Discussion:

Resolving patch:
diff --git a/edb/schema/inheriting.py b/edb/schema/inheriting.py
--- a/edb/schema/inheriting.py
+++ b/edb/schema/inheriting.py
@@ -203,12 +203,26 @@ def get_inherited_ref_layout(
 
         ancestors = set(self.scls.get_ancestors(schema).objects(schema))
         for base in bases.objects(schema) + (self.scls,):
-            base_refs: so.ObjectIndexBase[
+            base_refs: Dict[
                 sn.Name,
                 s_referencing.ReferencedInheritingObject,
-            ] = base.get_field_value(schema, attr)
-
-            for k, v in base_refs.items(schema):
+            ] = dict(base.get_field_value(schema, attr).items(schema))
+
+            # Pointers can reference each other if they are computed,
+            # and if they are processed in the wrong order,
+            # recompiling expressions in inherit_field can break, so
+            # we need to sort them by cross refs.
+            # Since inherit_fields doesn't recompile expressions
+            # in transient derivations, we skip the sorting there.
+            if not context.transient_derivation:
+                rev_refs = {v: k for k, v in base_refs.items()}
+                base_refs = {
+                    rev_refs[v]: v
+                    for v in reversed(
+                        sd.sort_by_cross_refs(schema, base_refs.values()))
+                }
+
+            for k, v in base_refs.items():
                 if not v.should_propagate(schema):
                     continue
                 if base == self.scls and not v.get_owned(schema):


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
@@ -5834,6 +5834,17 @@ async def test_edgeql_ddl_property_computable_circular(self):
             };
         ''')
 
+    async def test_edgeql_ddl_property_computable_add_dep(self):
+        # Make sure things don't get messed up when we add new dependencies
+        await self.con.execute('''
+            create type A {
+                create property foo := "!";
+                create property bar -> str;
+            };
+            alter type A alter property foo using (.bar);
+            create type B extending A;
+        ''')
+
     async def test_edgeql_ddl_property_computable_bad_01(self):
         with self.assertRaisesRegex(
                 edgedb.InvalidPropertyTargetError,