Repository: edgedb/edgedb

Issue:
Rewrites aren't type-checked in the schema
```
edgedb> create type X { create property foo -> str { create rewrite insert using (10) } };
OK: CREATE TYPE
edgedb> insert X;
edgedb error: InvalidPropertyTargetError: invalid target for property 'foo' of object type 'default::X': 'std::int64' (expecting 'std::str')
```
We should fail at create rewrite time


Discussion:

Resolving patch:
diff --git a/edb/schema/rewrites.py b/edb/schema/rewrites.py
--- a/edb/schema/rewrites.py
+++ b/edb/schema/rewrites.py
@@ -130,13 +130,29 @@ def compile_expr_field(
     ) -> s_expr.CompiledExpression:
         if field.name == 'expr':
             from edb.ir import pathid
-            from . import pointers
+            from . import pointers as s_pointers
+            from . import objtypes as s_objtypes
+            from . import links as s_links
 
             parent_ctx = self.get_referrer_context_or_die(context)
             pointer = parent_ctx.op.get_object(schema, context)
-            assert isinstance(pointer, pointers.Pointer)
+            assert isinstance(pointer, s_pointers.Pointer)
+
             source = pointer.get_source(schema)
-            assert isinstance(source, s_types.Type)
+            if isinstance(source, s_objtypes.ObjectType):
+                subject = source
+            elif isinstance(source, s_links.Link):
+                subject = source.get_target(schema)
+                assert subject
+
+                source_context = self.get_attribute_source_context('expr')
+                raise errors.SchemaDefinitionError(
+                    'rewrites on link properties are not supported',
+                    context=source_context,
+                )
+            else:
+                raise NotImplementedError('unsupported rewrite source')
+
             # XXX: in_ddl_context_name is disabled for now because
             # it causes the compiler to reject DML; we might actually
             # want it for something, though, so we might need to
@@ -147,12 +163,12 @@ def compile_expr_field(
 
             kind = self._get_kind(schema)
 
-            anchors = {}
+            anchors: dict[str, s_types.Type | pathid.PathId] = {}
 
             # __subject__
             anchors["__subject__"] = pathid.PathId.from_type(
                 schema,
-                source,
+                subject,
                 typename=sn.QualName(module="__derived__", name="__subject__"),
             )
             # __specified__
@@ -162,7 +178,7 @@ def compile_expr_field(
                 named=True,
                 element_types={
                     pn.name: bool_type
-                    for pn in source.get_pointers(schema).keys(schema)
+                    for pn in subject.get_pointers(schema).keys(schema)
                 },
             )
             anchors['__specified__'] = specified_type
@@ -171,14 +187,12 @@ def compile_expr_field(
             if qltypes.RewriteKind.Update == kind:
                 anchors['__old__'] = pathid.PathId.from_type(
                     schema,
-                    source,
+                    subject,
                     typename=sn.QualName(module='__derived__', name='__old__'),
                 )
 
             singletons = frozenset(anchors.values())
 
-            assert isinstance(source, s_types.Type)
-
             return type(value).compiled(
                 value,
                 schema=schema,
@@ -216,11 +230,48 @@ def validate_object(
         schema: s_schema.Schema,
         context: sd.CommandContext,
     ) -> None:
-        # XXX: verify we don't have the same bug as access policies
-        # where linkprop defaults are broken.
-        # (I think we won't need to, since we'll operate after
-        # the *real* operations)
-        pass
+        from . import pointers as s_pointers
+
+        expr: s_expr.Expression = self.scls.get_expr(schema)
+
+        if not expr.irast:
+            expr = self.compile_expr_field(
+                schema, context, Rewrite.get_field('expr'), expr
+            )
+            assert expr.irast
+
+        ir = expr.irast
+        compiled_schema = ir.schema
+        typ: s_types.Type = ir.stype
+
+        if (
+            typ.is_view(compiled_schema)
+            # Using an alias/global always creates a new subtype view,
+            # but we want to allow those here, so check whether there
+            # is a shape more directly.
+            and not (
+                len(shape := ir.view_shapes.get(typ, [])) == 1
+                and shape[0].is_id_pointer(compiled_schema)
+            )
+        ):
+            source_context = self.get_attribute_source_context('expr')
+            raise errors.SchemaDefinitionError(
+                f'rewrite expression may not include a shape',
+                context=source_context,
+            )
+
+        pointer = self.scls.get_subject(compiled_schema)
+        assert isinstance(pointer, s_pointers.Pointer)
+        ptr_target = pointer.get_target(compiled_schema)
+        assert ptr_target
+        if not typ.assignment_castable_to(ptr_target, compiled_schema):
+            source_context = self.get_attribute_source_context('expr')
+            raise errors.SchemaDefinitionError(
+                f'rewrite expression is of invalid type: '
+                f'{typ.get_displayname(compiled_schema)}, '
+                f'expected {ptr_target.get_displayname(compiled_schema)}',
+                context=source_context,
+            )
 
     @classmethod
     def _cmd_tree_from_ast(


Test patch:
diff --git a/tests/test_edgeql_rewrites.py b/tests/test_edgeql_rewrites.py
--- a/tests/test_edgeql_rewrites.py
+++ b/tests/test_edgeql_rewrites.py
@@ -978,3 +978,51 @@ async def test_edgeql_rewrites_24(self):
             'select X { tup }',
             [{'tup': (1, '2')}],
         )
+
+    async def test_edgeql_rewrites_25(self):
+        async with self.assertRaisesRegexTx(
+            edgedb.SchemaDefinitionError,
+            r"rewrite expression is of invalid type",
+        ):
+            await self.con.execute(
+                '''
+                create type X {
+                    create property foo -> str {
+                        create rewrite insert using (10);
+                    };
+                };
+                '''
+            )
+
+        async with self.assertRaisesRegexTx(
+            edgedb.SchemaDefinitionError,
+            r"rewrite expression may not include a shape",
+        ):
+            await self.con.execute(
+                '''
+                create type X {
+                    create link foo -> std::Object {
+                        create rewrite insert using (
+                            (select std::Object { __type__: {name} })
+                        );
+                    };
+                };
+                '''
+            )
+
+    async def test_edgeql_rewrites_26(self):
+        async with self.assertRaisesRegexTx(
+            edgedb.SchemaDefinitionError,
+            r"rewrites on link properties are not supported",
+        ):
+            await self.con.execute(
+                '''
+                create type X {
+                    create link foo -> std::Object {
+                        create property bar: int32 {
+                            create rewrite insert using ('hello');
+                        };
+                    };
+                };
+                '''
+            )