Skip to content

Commit

Permalink
Restrict usage of abstract scalars and anytype.
Browse files Browse the repository at this point in the history
Casting into an abstract scalar or anytype is now forbidden.

Technically anytype is a valid type that is the supertype for both
anyscalar and Object. However, it is currently illegal to use it
anywhere outside of a function signature definition.

Anytype is also special in the fact that it doesn't have a corresponding
entry in the schema module (no Type object).
  • Loading branch information
vpetrovykh authored and 1st1 committed Nov 30, 2018
1 parent 2ecefc5 commit a23af17
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 5 deletions.
11 changes: 10 additions & 1 deletion edb/lang/edgeql/compiler/inference/types.py
Expand Up @@ -298,7 +298,16 @@ def __infer_typeref(ir, env):

@_infer_type.register(irast.TypeCast)
def __infer_typecast(ir, env):
return infer_type(ir.type, env)
stype = infer_type(ir.type, env)

# is_polymorphic is synonymous to get_is_abstract for scalars
if stype.is_polymorphic(env.schema):
raise ql_errors.EdgeQLError(
f'cannot cast into an abstract scalar '
f'{stype.get_displayname(env.schema)}',
context=ir.context)

return stype


@_infer_type.register(irast.Stmt)
Expand Down
3 changes: 3 additions & 0 deletions edb/lang/edgeql/compiler/schemactx.py
Expand Up @@ -29,6 +29,7 @@
from edb.lang.schema import nodes as s_nodes
from edb.lang.schema import objects as s_obj
from edb.lang.schema import pointers as s_pointers
from edb.lang.schema import pseudo as s_pseudo
from edb.lang.schema import sources as s_sources
from edb.lang.schema import types as s_types
from edb.lang.schema import utils as s_utils
Expand All @@ -51,6 +52,8 @@ def get_schema_object(
srcctx = name.context
module = name.module
name = name.name
elif isinstance(name, qlast.AnyType):
return s_pseudo.Any.create()

if module:
name = sn.Name(name=name, module=module)
Expand Down
6 changes: 6 additions & 0 deletions edb/lang/edgeql/compiler/typegen.py
Expand Up @@ -106,6 +106,12 @@ def _ql_typeref_to_ir_typeref(
typ.subtypes.append(subtype)
else:
styp = schemactx.get_schema_type(maintype, ctx=ctx)

if styp.contains_any():
raise errors.EdgeQLSyntaxError(
f"Unexpected 'anytype'",
context=maintype.context)

typ = irast.TypeRef(
maintype=styp.get_name(ctx.env.schema),
subtypes=[]
Expand Down
2 changes: 1 addition & 1 deletion edb/lang/edgeql/parser/grammar/expressions.py
Expand Up @@ -1264,7 +1264,7 @@ def reduce_SimpleTypeName(self, *kids):
self.val = kids[0].val

def reduce_TYPEOF_Expr(self, *kids):
self.val = qlast.TypeOf(expr=kids[0].val)
self.val = qlast.TypeOf(expr=kids[1].val)

def reduce_LPAREN_FullTypeExpr_RPAREN(self, *kids):
self.val = kids[1].val
Expand Down
3 changes: 3 additions & 0 deletions edb/lang/schema/pseudo.py
Expand Up @@ -50,6 +50,9 @@ def get__virtual_children(self, schema):
def get_bases(self, schema):
return so.ObjectList.create_empty()

def get_is_abstract(self, schema):
return True

def is_any(self):
return True

Expand Down
3 changes: 1 addition & 2 deletions edb/lang/schema/scalars.py
Expand Up @@ -82,8 +82,7 @@ def is_scalar(self):
return True

def is_polymorphic(self, schema):
return bool(self.get_is_abstract(schema) and
self.get_name(schema).module == 'std')
return self.get_is_abstract(schema)

def _resolve_polymorphic(self, schema, concrete_type: s_types.Type):
if (self.is_polymorphic(schema) and
Expand Down
6 changes: 6 additions & 0 deletions edb/lang/schema/types.py
Expand Up @@ -80,6 +80,9 @@ def is_polymorphic(self, schema):
def is_any(self):
return False

def contains_any(self):
return self.is_any()

def is_scalar(self):
return False

Expand Down Expand Up @@ -211,6 +214,9 @@ def is_polymorphic(self, schema):
return any(st.is_polymorphic(schema)
for st in self.get_subtypes())

def contains_any(self):
return any(st.contains_any() for st in self.get_subtypes())

@property
def is_virtual(self):
# This property in necessary for compatibility with node classes.
Expand Down
3 changes: 2 additions & 1 deletion tests/test_edgeql_calls.py
Expand Up @@ -1027,13 +1027,14 @@ async def test_edgeql_calls_28(self):
);
''')

@unittest.expectedFailure
async def test_edgeql_calls_29(self):
await self.con.execute('''
CREATE FUNCTION test::call29(
a: anyint
) -> anyint
FROM EdgeQL $$
SELECT a + <anyint>1
SELECT a + 1
$$;
''')

Expand Down
82 changes: 82 additions & 0 deletions tests/test_edgeql_casts.py
@@ -0,0 +1,82 @@
#
# This source file is part of the EdgeDB open source project.
#
# Copyright 2018-present MagicStack Inc. and the EdgeDB authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


import unittest # NOQA

from edb.client import exceptions as exc
from edb.server import _testbase as tb


class TestEdgeQLCasts(tb.QueryTestCase):
# casting into an abstract scalar should be illegal
async def test_edgeql_casts_illegal_01(self):
with self.assertRaisesRegex(
exc.EdgeQLSyntaxError, r"Unexpected 'anytype'"):
await self.con.execute("""
SELECT <anytype>123;
""")

async def test_edgeql_casts_illegal_02(self):
with self.assertRaisesRegex(
exc.EdgeQLError, r'cannot cast.*abstract'):
await self.con.execute("""
SELECT <anyscalar>123;
""")

async def test_edgeql_casts_illegal_03(self):
with self.assertRaisesRegex(
exc.EdgeQLError, r'cannot cast.*abstract'):
await self.con.execute("""
SELECT <anyreal>123;
""")

async def test_edgeql_casts_illegal_04(self):
with self.assertRaisesRegex(
exc.EdgeQLError, r'cannot cast.*abstract'):
await self.con.execute("""
SELECT <anyint>123;
""")

async def test_edgeql_casts_illegal_05(self):
with self.assertRaisesRegex(
exc.EdgeQLError, r'cannot cast.*abstract'):
await self.con.execute("""
SELECT <anyfloat>123;
""")

async def test_edgeql_casts_illegal_06(self):
with self.assertRaisesRegex(
exc.EdgeQLError, r'cannot cast.*abstract'):
await self.con.execute("""
SELECT <sequence>123;
""")

async def test_edgeql_casts_illegal_07(self):
with self.assertRaisesRegex(
exc.EdgeQLSyntaxError, r"Unexpected 'anytype'"):
await self.con.execute("""
SELECT <array<anytype>>[123];
""")

async def test_edgeql_casts_illegal_08(self):
with self.assertRaisesRegex(
exc.EdgeQLSyntaxError, r"Unexpected 'anytype'"):
await self.con.execute("""
SELECT <tuple<int64, anytype>>(123, 123);
""")
103 changes: 103 additions & 0 deletions tests/test_edgeql_ddl.py
Expand Up @@ -1297,3 +1297,106 @@ async def test_edgeql_ddl_attribute_02(self):
''', [
[{"attributes": [{"name": "test::attr2", "@value": "aaaa"}]}]
])

# FIXME: the error messages are currently unclear
@unittest.expectedFailure
async def test_edgeql_ddl_anytype_01(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"invalid property target"):

await self.con.execute("""
CREATE ABSTRACT LINK test::test_object_link_prop {
CREATE PROPERTY test::link_prop1 -> anytype;
};
""")

@unittest.expectedFailure
async def test_edgeql_ddl_anytype_02(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"invalid link target"):

await self.con.execute("""
CREATE TYPE test::AnyObject2 {
CREATE LINK test::a -> anytype;
};
""")

@unittest.expectedFailure
async def test_edgeql_ddl_anytype_03(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"invalid property target"):

await self.con.execute("""
CREATE TYPE test::AnyObject3 {
CREATE PROPERTY test::a -> anytype;
};
""")

@unittest.expectedFailure
async def test_edgeql_ddl_anytype_04(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"invalid property target"):

await self.con.execute("""
CREATE TYPE test::AnyObject4 {
CREATE PROPERTY test::a -> anyscalar;
};
""")

@unittest.expectedFailure
async def test_edgeql_ddl_anytype_05(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"invalid property target"):

await self.con.execute("""
CREATE TYPE test::AnyObject5 {
CREATE PROPERTY test::a -> anyint;
};
""")

@unittest.expectedFailure
async def test_edgeql_ddl_anytype_06(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"invalid.*anytype"):

await self.con.execute("""
CREATE TYPE test::AnyObject6 EXTENDING anytype {
CREATE REQUIRED LINK test::a -> test::AnyObject6;
CREATE REQUIRED PROPERTY test::b -> str;
};
""")

async def test_edgeql_ddl_extending_01(self):
with self.assertRaisesRegex(
client_errors.SchemaError,
r"Could not find consistent MRO for test::Merged1"):

await self.con.execute(r"""
CREATE TYPE test::ExtA1;
CREATE TYPE test::ExtB1;
# create two types with incompatible linearized bases
CREATE TYPE test::ExtC1 EXTENDING (test::ExtA1, test::ExtB1);
CREATE TYPE test::ExtD1 EXTENDING (test::ExtB1, test::ExtA1);
# extending from both of these incompatible types
CREATE TYPE test::Merged1 EXTENDING (test::ExtC1, test::ExtD1);
""")

async def test_edgeql_ddl_extending_02(self):
async with self._run_and_rollback():
await self.con.execute(r"""
CREATE TYPE test::ExtA2;
# Create two types with a different position of Object
# in the bases. This doesn't impact the linearized
# bases because Object is already implicitly included
# as the first element of the base types.
CREATE TYPE test::ExtC2 EXTENDING (test::ExtA1, Object);
CREATE TYPE test::ExtD2 EXTENDING (Object, test::ExtA1);
# extending from both of these types
CREATE TYPE test::Merged2 EXTENDING (test::ExtC2, test::ExtD2);
""")

0 comments on commit a23af17

Please sign in to comment.