Repository: edgedb/edgedb

Issue:
`__typename__` injection on queries in std modules raises `SchemaDefinitionError`
```
edgedb> SELECT INTROSPECT TYPEOF schema::ObjectType.properties.target;
edgedb error: SchemaDefinitionError: cannot alter object type 'schema::ObjectType': module schema is read-only
```


Discussion:
Similar to #2387.

Resolving patch:
diff --git a/edb/edgeql/compiler/expr.py b/edb/edgeql/compiler/expr.py
--- a/edb/edgeql/compiler/expr.py
+++ b/edb/edgeql/compiler/expr.py
@@ -67,19 +67,7 @@ def compile__Optional(
 @dispatch.compile.register(qlast.Path)
 def compile_Path(
         expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set:
-    ir = setgen.compile_path(expr, ctx=ctx)
-    # We call compile_query_subject in order to create a new view for
-    # injecting properties if needed. This will only happen if
-    # expr_exposed, so stmt code paths that don't want a new view
-    # created (because there is a shape already specified or because
-    # it wants to create its own new view in its compile_query_subject call)
-    # should make sure expr_exposed is false.
-    # (Strictly speaking, the expr_exposed check here is redundant, but it
-    # saves on some pointless work in compile_query_subject.)
-    if ctx.expr_exposed:
-        ir = stmt.compile_query_subject(
-            ir, allow_select_shape_inject=True, compile_views=False, ctx=ctx)
-    return ir
+    return stmt.maybe_add_view(setgen.compile_path(expr, ctx=ctx), ctx=ctx)
 
 
 @dispatch.compile.register(qlast.BinOp)
@@ -582,7 +570,8 @@ def compile_Introspect(
             f'cannot introspect generic types',
             context=expr.type.context)
 
-    return setgen.ensure_set(irast.TypeIntrospection(typeref=typeref), ctx=ctx)
+    ir = setgen.ensure_set(irast.TypeIntrospection(typeref=typeref), ctx=ctx)
+    return stmt.maybe_add_view(ir, ctx=ctx)
 
 
 @dispatch.compile.register(qlast.Indirection)
diff --git a/edb/edgeql/compiler/func.py b/edb/edgeql/compiler/func.py
--- a/edb/edgeql/compiler/func.py
+++ b/edb/edgeql/compiler/func.py
@@ -49,6 +49,7 @@
 from . import polyres
 from . import schemactx
 from . import setgen
+from . import stmt
 from . import typegen
 
 if TYPE_CHECKING:
@@ -270,7 +271,8 @@ def compile_FunctionCall(
         tuple_path_ids=tuple_path_ids,
     )
 
-    return setgen.ensure_set(fcall, typehint=rtype, path_id=path_id, ctx=ctx)
+    ir_set = setgen.ensure_set(fcall, typehint=rtype, path_id=path_id, ctx=ctx)
+    return stmt.maybe_add_view(ir_set, ctx=ctx)
 
 
 #: A dictionary of conditional callables and the indices
diff --git a/edb/edgeql/compiler/stmt.py b/edb/edgeql/compiler/stmt.py
--- a/edb/edgeql/compiler/stmt.py
+++ b/edb/edgeql/compiler/stmt.py
@@ -1212,6 +1212,28 @@ def compile_query_subject(
     return expr
 
 
+def maybe_add_view(ir: irast.Set, *, ctx: context.ContextLevel) -> irast.Set:
+    """Possibly wrap ir in a new view, if needed for tid/tname injection
+
+    This should be called by every ast leaf compilation that can originate
+    an object type.
+    """
+
+    # We call compile_query_subject in order to create a new view for
+    # injecting properties if needed. This will only happen if
+    # expr_exposed, so stmt code paths that don't want a new view
+    # created (because there is a shape already specified or because
+    # it wants to create its own new view in its compile_query_subject call)
+    # should make sure expr_exposed is false.
+    #
+    # The checks here are microoptimizations.
+    if ctx.expr_exposed and ir.path_id.is_objtype_path():
+        return compile_query_subject(
+            ir, allow_select_shape_inject=True, compile_views=False, ctx=ctx)
+    else:
+        return ir
+
+
 def compile_groupby_clause(
         groupexprs: Iterable[qlast.Base], *,
         ctx: context.ContextLevel) -> List[irast.Set]:


Test patch:
diff --git a/tests/test_edgeql_calls.py b/tests/test_edgeql_calls.py
--- a/tests/test_edgeql_calls.py
+++ b/tests/test_edgeql_calls.py
@@ -1659,3 +1659,18 @@ async def test_edgeql_calls_obj_03(self):
                     fight(Scorpion, SubZero);SELECT area(Shape)
                 """,
             )
+
+    async def test_edgeql_call_builtin_obj(self):
+        await self.con.execute(
+            r"""
+                CREATE FUNCTION get_obj(name: str) ->
+                  SET OF schema::Object USING (
+                    SELECT schema::Object FILTER .name = name);
+            """,
+        )
+
+        res = await self.con._fetchall("""
+            SELECT get_obj('std::BaseObject')
+        """, __typenames__=True)
+        self.assertEqual(len(res), 1)
+        self.assertEqual(res[0].__tname__, "schema::ObjectType")
diff --git a/tests/test_edgeql_expressions.py b/tests/test_edgeql_expressions.py
--- a/tests/test_edgeql_expressions.py
+++ b/tests/test_edgeql_expressions.py
@@ -5202,3 +5202,18 @@ async def test_edgeql_assert_distinct_no_op(self):
                 single foo := assert_distinct(.name)
             }
         """)
+
+    async def test_edgeql_introspect_without_shape(self):
+        await self.assert_query_result(
+            """
+                SELECT (INTROSPECT TYPEOF BaseObject)
+            """,
+            [
+                {"id": {}}
+            ]
+        )
+        res = await self.con._fetchall("""
+            SELECT (INTROSPECT TYPEOF BaseObject)
+        """, __typenames__=True)
+        self.assertEqual(len(res), 1)
+        self.assertEqual(res[0].__tname__, "schema::ObjectType")