Repository: edgedb/edgedb

Issue:
Improve error messages for link constraints
Related: https://edgedb.slack.com/archives/CNECHA9EW/p1634334309357400

#### When accessing a property that *exists* on the link target (like `is_active`):

```
type Person {
  property is_active -> bool;
}

type GroupChat {
  multi link members -> Person {
    constraint expression on (__subject__.is_active = true);
  }
}
```

You get this message, this isn't applicable in this scenario (there isn't more than one hop happening). The compiler seems to think `__subject__` is pointing to the link target, not the link itself.

```
$ edgedb migration create
error: constraints cannot contain paths with more than one hop
   ┌─ ./dbschema/default.esdl:18:7
   │
18 │       constraint expression on (__subject__.is_active = true);
   │       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error
edgedb error: cannot proceed until .esdl files are fixed
```

####  When trying to reference a property that doesn't exist:

```
type Person {
  property is_active -> bool;
}

type GroupChat {
  multi link members -> Person {
    constraint expression on (__subject__.not_a_prop = true);
  }
}
```

You get this message, which seems to indicate that `__subject__` is of type `User`

```
$ edgedb migration create
error: object type 'default::User' has no link or property 'not_a_prop'
   ┌─ ./dbschema/default.esdl:69:44
   │
69 │       constraint expression on (__subject__.not_a_prop = true);
   │                                            ^^^^^^^^^^^ error
edgedb error: cannot proceed until .esdl files are fixed
```


Discussion:

Resolving patch:
diff --git a/edb/schema/constraints.py b/edb/schema/constraints.py
--- a/edb/schema/constraints.py
+++ b/edb/schema/constraints.py
@@ -650,6 +650,7 @@ def _populate_concrete_constraint_attrs(
         from edb.ir import ast as ir_ast
         from edb.ir import utils as ir_utils
         from . import pointers as s_pointers
+        from . import links as s_links
 
         bases = self.get_resolved_attribute_value(
             'bases', schema=schema, context=context,
@@ -794,11 +795,18 @@ def _populate_concrete_constraint_attrs(
                                        ir_ast.TupleIndirectionPointerRef)
                             and rptr.ptrref.source_ptr is None
                             and rptr.source.rptr is not None):
-                        raise errors.InvalidConstraintDefinitionError(
-                            "constraints cannot contain paths with more "
-                            "than one hop",
-                            context=sourcectx
-                        )
+                        if isinstance(subject, s_links.Link):
+                            raise errors.InvalidConstraintDefinitionError(
+                                "link constraints may not access "
+                                "the link target",
+                                context=sourcectx
+                            )
+                        else:
+                            raise errors.InvalidConstraintDefinitionError(
+                                "constraints cannot contain paths with more "
+                                "than one hop",
+                                context=sourcectx
+                            )
 
                     ref = rptr.source
 


Test patch:
diff --git a/tests/test_constraints.py b/tests/test_constraints.py
--- a/tests/test_constraints.py
+++ b/tests/test_constraints.py
@@ -1133,7 +1133,7 @@ async def test_constraints_ddl_08(self):
 
         async with self.assertRaisesRegexTx(
             edgedb.InvalidConstraintDefinitionError,
-            "constraints cannot contain paths with more than one hop",
+            "link constraints may not access the link target",
         ):
             await self.con.execute("""
                 ALTER TYPE ObjCnstr2 {