def reduce_START_TRANSACTION_OptTransactionModeList(self, *kids): modes = kids[2].val isolation = None access = None deferrable = None for mode, mode_ctx in modes: if isinstance(mode, qltypes.TransactionIsolationLevel): if isolation is not None: raise errors.EdgeQLSyntaxError( f"only one isolation level can be specified", context=mode_ctx) isolation = mode elif isinstance(mode, qltypes.TransactionAccessMode): if access is not None: raise errors.EdgeQLSyntaxError( f"only one access mode can be specified", context=mode_ctx) access = mode else: assert isinstance(mode, qltypes.TransactionDeferMode) if deferrable is not None: raise errors.EdgeQLSyntaxError( f"deferrable mode can only be specified once", context=mode_ctx) deferrable = mode self.val = qlast.StartTransaction(isolation=isolation, access=access, deferrable=deferrable)
def _process_role_body( cls, cmd: sd.Command, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> None: password = cmd.get_attribute_value('password') if password is not None: if cmd.get_attribute_value('password_hash') is not None: raise errors.EdgeQLSyntaxError( 'cannot specify both `password` and `password_hash` in' ' the same statement', context=astnode.context, ) salted_password = scram.build_verifier(password) cmd.set_attribute_value('password', salted_password) password_hash = cmd.get_attribute_value('password_hash') if password_hash is not None: try: scram.parse_verifier(password_hash) except ValueError as e: raise errors.InvalidValueError( e.args[0], context=astnode.context) cmd.set_attribute_value('password', password_hash)
def _ql_typeexpr_to_type(ql_t: qlast.TypeExpr, *, ctx: context.ContextLevel) -> List[s_types.Type]: if isinstance(ql_t, qlast.TypeOf): with ctx.newscope(fenced=True, temporary=True) as subctx: ir_set = setgen.ensure_set(dispatch.compile(ql_t.expr, ctx=subctx), ctx=subctx) stype = setgen.get_set_type(ir_set, ctx=subctx) return [stype] elif isinstance(ql_t, qlast.TypeOp): if ql_t.op == '|': return (_ql_typeexpr_to_type(ql_t.left, ctx=ctx) + _ql_typeexpr_to_type(ql_t.right, ctx=ctx)) raise errors.UnsupportedFeatureError( f'type operator {ql_t.op!r} is not implemented', context=ql_t.context) elif isinstance(ql_t, qlast.TypeName): return [_ql_typename_to_type(ql_t, ctx=ctx)] else: raise errors.EdgeQLSyntaxError("Unexpected type expression", context=ql_t.context)
def _ql_typeexpr_to_type(ql_t: qlast.TypeExpr, *, ctx: context.ContextLevel) -> List[s_types.Type]: if isinstance(ql_t, qlast.TypeOf): with ctx.new() as subctx: # Use an empty scope tree, to avoid polluting things pointlessly subctx.path_scope = irast.ScopeTreeNode() ir_set = dispatch.compile(ql_t.expr, ctx=subctx) stype = setgen.get_set_type(ir_set, ctx=subctx) return [stype] elif isinstance(ql_t, qlast.TypeOp): if ql_t.op == '|': return (_ql_typeexpr_to_type(ql_t.left, ctx=ctx) + _ql_typeexpr_to_type(ql_t.right, ctx=ctx)) raise errors.UnsupportedFeatureError( f'type operator {ql_t.op!r} is not implemented', context=ql_t.context) elif isinstance(ql_t, qlast.TypeName): return [_ql_typename_to_type(ql_t, ctx=ctx)] else: raise errors.EdgeQLSyntaxError("Unexpected type expression", context=ql_t.context)
def _extract_target(self, target, cmds, context, *, overloaded=False): if target: return target, cmds for cmd in cmds: if isinstance(cmd, qlast.SetField) and cmd.name == 'expr': if target is not None: raise errors.EdgeQLSyntaxError( f'computable link with more than one expression', context=context) target = cmd.value if not overloaded and target is None: raise errors.EdgeQLSyntaxError( f'computable link without expression', context=context) return target, cmds
def normalize(eql: str) -> Entry: try: return _normalize(eql) except TokenizerError as e: message, position = e.args hint = _derive_hint(eql, message, position) raise errors.EdgeQLSyntaxError( message, position=position, hint=hint) from e
def _validate(self): on_target_delete = None for cmd in self.val.commands: if isinstance(cmd, qlast.OnTargetDelete): if on_target_delete: raise errors.EdgeQLSyntaxError( f"more than one 'on target delete' specification", context=cmd.context) else: on_target_delete = cmd
def reduce_MODULE_ModuleName_SDLCommandBlock(self, *kids): # Check that top-level declarations DO NOT use fully-qualified # names and aren't nested module blocks. declarations = kids[2].val for decl in declarations: if isinstance(decl, qlast.ModuleDeclaration): raise errors.EdgeQLSyntaxError( "nested module declaration is not allowed", context=decl.context) elif decl.name.module is not None: raise errors.EdgeQLSyntaxError( "fully-qualified name is not allowed in " "a module declaration", context=decl.name.context) self.val = qlast.ModuleDeclaration( # mirror what we do in CREATE MODULE name=qlast.ObjectRef(module=None, name='.'.join(kids[1].val)), declarations=declarations, )
def _cmd_tree_from_ast(cls, schema, astnode, context): cmd = super()._cmd_tree_from_ast(schema, astnode, context) if not astnode.superuser and not context.testmode: raise errors.EdgeQLSyntaxError( 'missing required SUPERUSER qualifier', context=astnode.context, ) cmd.set_attribute_value('is_superuser', astnode.superuser) cls._process_role_body(cmd, schema, astnode, context) return cmd
def register_set_in_scope(ir_set: irast.Set, *, path_scope: irast.ScopeTreeNode = None, ctx: context.CompilerContext) -> None: if path_scope is None: path_scope = ctx.path_scope try: path_scope.attach_path(ir_set.path_id) except irast.InvalidScopeConfiguration as e: raise errors.EdgeQLSyntaxError(e.args[0], context=ir_set.context) from e
def _ql_typeexpr_to_type(ql_t: qlast.TypeExpr, *, ctx: context.ContextLevel) -> List[s_types.Type]: if isinstance(ql_t, qlast.TypeOf): with ctx.new() as subctx: # Use an empty scope tree, to avoid polluting things pointlessly subctx.path_scope = irast.ScopeTreeNode() subctx.expr_exposed = context.Exposure.UNEXPOSED ir_set = dispatch.compile(ql_t.expr, ctx=subctx) stype = setgen.get_set_type(ir_set, ctx=subctx) return [stype] elif isinstance(ql_t, qlast.TypeOp): if ql_t.op == '|': # We need to validate that type ops are applied only to # object types. So we check the base case here, when the # left or right operand is a single type, because if it's # a longer list, then we know that it was already composed # of "|" or "&", or it is the result of inference by # "typeof" and is a list of object types anyway. left = _ql_typeexpr_to_type(ql_t.left, ctx=ctx) right = _ql_typeexpr_to_type(ql_t.right, ctx=ctx) if len(left) == 1 and not left[0].is_object_type(): raise errors.UnsupportedFeatureError( f'cannot use type operator {ql_t.op!r} with non-object ' f'type {left[0].get_displayname(ctx.env.schema)}', context=ql_t.left.context) if len(right) == 1 and not right[0].is_object_type(): raise errors.UnsupportedFeatureError( f'cannot use type operator {ql_t.op!r} with non-object ' f'type {right[0].get_displayname(ctx.env.schema)}', context=ql_t.right.context) return left + right raise errors.UnsupportedFeatureError( f'type operator {ql_t.op!r} is not implemented', context=ql_t.context) elif isinstance(ql_t, qlast.TypeName): return [_ql_typename_to_type(ql_t, ctx=ctx)] else: raise errors.EdgeQLSyntaxError("Unexpected type expression", context=ql_t.context)
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> sd.Command: assert isinstance(astnode, qlast.CreateRole) cmd = super()._cmd_tree_from_ast(schema, astnode, context) if not astnode.superuser and not context.testmode: raise errors.EdgeQLSyntaxError( 'missing required SUPERUSER qualifier', context=astnode.context, ) cmd.set_attribute_value('superuser', astnode.superuser) cls._process_role_body(cmd, schema, astnode, context) return cmd
def get_exception(self, native_err, context, token=None): msg = native_err.args[0] if isinstance(native_err, errors.EdgeQLSyntaxError): return native_err else: if msg.startswith('Unexpected token: '): token = token or getattr(native_err, 'token', None) if not token or token.kind() == 'EOF': msg = 'Unexpected end of line' elif hasattr(token, 'val'): msg = f'Unexpected {token.val!r}' elif token.kind() == 'NL': msg = 'Unexpected end of line' else: msg = f'Unexpected {token.text()!r}' return errors.EdgeQLSyntaxError(msg, context=context, token=token)
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> CreateDatabase: cmd = super()._cmd_tree_from_ast(schema, astnode, context) assert isinstance(cmd, CreateDatabase) assert isinstance(astnode, qlast.CreateDatabase) if astnode.template is not None: if not context.testmode: raise errors.EdgeQLSyntaxError( f'unexpected {astnode.template.name!r}', context=astnode.template.context, ) cmd.template = astnode.template.name return cmd
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> sd.Command: cmd = super()._cmd_tree_from_ast(schema, astnode, context) assert isinstance(astnode, qlast.AlterFunction) if astnode.code is not None: if ( astnode.code.language is not qlast.Language.EdgeQL or astnode.code.from_function is not None or astnode.code.from_expr ): raise errors.EdgeQLSyntaxError( 'altering function code is only supported for ' 'pure EdgeQL functions', context=astnode.context ) nativecode_expr: Optional[qlast.Expr] = None if astnode.nativecode is not None: nativecode_expr = astnode.nativecode elif astnode.code.code is not None: nativecode_expr = qlparser.parse(astnode.code.code) if nativecode_expr is not None: nativecode = expr.Expression.from_ast( nativecode_expr, schema, context.modaliases, ) cmd.set_attribute_value( 'nativecode', nativecode, ) return cmd
def get_exception(self, native_err, context, token=None): msg = native_err.args[0] details = None hint = None if isinstance(native_err, errors.EdgeQLSyntaxError): return native_err else: if msg.startswith('Unexpected token: '): token = token or getattr(native_err, 'token', None) token_kind = token.kind() ltok = self.parser._stack[-1][0] is_reserved = ( token.text().lower() in gr_keywords.by_type[gr_keywords.RESERVED_KEYWORD]) # Look at the parsing stack and use tokens and # non-terminals to infer the parser rule when the # error occurred. i, rule = self._get_rule() if not token or token_kind == 'EOF': msg = 'Unexpected end of line' elif (rule == 'shape' and token_kind == 'IDENT' and isinstance(ltok, parsing.Nonterm)): # Make sure that the previous element in the stack # is some kind of Nonterminal, because if it's # not, this is probably not an issue of a missing # COMMA. hint = (f"It appears that a ',' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule == 'list of arguments' and # The stack is like <NodeName> LPAREN <AnyIdentifier> i == 1 and isinstance( ltok, (gr_exprs.AnyIdentifier, tokens.T_WITH, tokens.T_SELECT, tokens.T_FOR, tokens.T_INSERT, tokens.T_UPDATE, tokens.T_DELETE))): hint = ("Missing parentheses around statement used " "as an expression") # We want the error context correspond to the # statement keyword context = ltok.context token = None elif (rule == 'array slice' and # The offending token was something that could # make an expression token_kind in {'IDENT', 'ICONST'} and not isinstance(ltok, tokens.T_COLON)): hint = (f"It appears that a ':' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule in {'list of arguments', 'tuple', 'array'} and # The offending token was something that could # make an expression token_kind in { 'IDENT', 'TRUE', 'FALSE', 'ICONST', 'FCONST', 'NICONST', 'NFCONST', 'BCONST', 'SCONST', } and not isinstance(ltok, tokens.T_COMMA)): hint = (f"It appears that a ',' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule == 'definition' and token_kind == 'IDENT'): # Something went wrong in a definition, so check # if the last successful token is a keyword. if (isinstance(ltok, gr_exprs.Identifier) and ltok.val.upper() == 'INDEX'): msg = (f"Expected 'ON', but got {token.text()!r} " f"instead") else: msg = f'Unexpected {token.text()!r}' elif rule == 'for iterator': msg = ("Missing parentheses around complex expression in " "a FOR iterator clause") if i > 0: context = pctx.merge_context([ self.parser._stack[-i][0].context, context, ]) token = None elif hasattr(token, 'val'): msg = f'Unexpected {token.val!r}' elif token_kind == 'NL': msg = 'Unexpected end of line' elif is_reserved and not isinstance(ltok, gr_exprs.Expr): # Another token followed by a reserved keyword: # likely an attempt to use keyword as identifier msg = f'Unexpected keyword {token.text()!r}' details = ( f'Token {token.text()!r} is a reserved keyword and' f' cannot be used as an identifier') hint = ( f'Use a different identifier or quote the name with' f' backticks: `{token.text()}`') else: msg = f'Unexpected {token.text()!r}' return errors.EdgeQLSyntaxError(msg, details=details, hint=hint, context=context, token=token)
def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: """Create an ir.Set representing the given EdgeQL path expression.""" anchors = ctx.anchors if expr.partial: if ctx.partial_path_prefix is not None: path_tip = ctx.partial_path_prefix else: raise errors.QueryError( 'could not resolve partial path ', context=expr.context) extra_scopes = {} computables = [] path_sets = [] for i, step in enumerate(expr.steps): if isinstance(step, qlast.SpecialAnchor): path_tip = resolve_special_anchor(step, ctx=ctx) elif isinstance(step, qlast.ObjectRef): if i > 0: # pragma: no cover raise RuntimeError( 'unexpected ObjectRef as a non-first path item') refnode = None if not step.module and step.name not in ctx.aliased_views: # Check if the starting path label is a known anchor refnode = anchors.get(step.name) if refnode is not None: path_tip = new_set_from_set( refnode, preserve_scope_ns=True, ctx=ctx) else: stype = schemactx.get_schema_type( step, condition=lambda o: ( isinstance(o, s_types.Type) and (o.is_object_type() or o.is_view(ctx.env.schema)) ), label='object type or alias', srcctx=step.context, ctx=ctx, ) if (stype.get_expr_type(ctx.env.schema) is not None and stype.get_name(ctx.env.schema) not in ctx.view_nodes): # This is a schema-level view, as opposed to # a WITH-block or inline alias view. stype = stmtctx.declare_view_from_schema(stype, ctx=ctx) view_set = ctx.view_sets.get(stype) if view_set is not None: view_scope_info = ctx.path_scope_map[view_set] path_tip = new_set_from_set( view_set, preserve_scope_ns=( view_scope_info.pinned_path_id_ns is not None ), ctx=ctx, ) extra_scopes[path_tip] = view_scope_info else: path_tip = class_set(stype, ctx=ctx) view_scls = ctx.class_view_overrides.get(stype.id) if (view_scls is not None and view_scls != get_set_type(path_tip, ctx=ctx)): path_tip = ensure_set( path_tip, type_override=view_scls, ctx=ctx) elif isinstance(step, qlast.Ptr): # Pointer traversal step ptr_expr = step if ptr_expr.direction is not None: direction = s_pointers.PointerDirection(ptr_expr.direction) else: direction = s_pointers.PointerDirection.Outbound ptr_name = ptr_expr.ptr.name source: s_obj.Object ptr: s_pointers.PointerLike if ptr_expr.type == 'property': # Link property reference; the source is the # link immediately preceding this step in the path. if path_tip.rptr is None: raise errors.EdgeQLSyntaxError( f"unexpected reference to link property {ptr_name!r} " "outside of a path expression", context=ptr_expr.ptr.context, ) if isinstance(path_tip.rptr.ptrref, irast.TypeIntersectionPointerRef): ind_prefix, ptrs = typegen.collapse_type_intersection_rptr( path_tip, ctx=ctx, ) prefix_type = get_set_type(ind_prefix.rptr.source, ctx=ctx) assert isinstance(prefix_type, s_sources.Source) if not ptrs: tip_type = get_set_type(path_tip, ctx=ctx) s_vn = prefix_type.get_verbosename(ctx.env.schema) t_vn = tip_type.get_verbosename(ctx.env.schema) ptr = ind_prefix.rptr.ptrref.shortname.name if direction is s_pointers.PointerDirection.Inbound: s_vn, t_vn = t_vn, s_vn raise errors.InvalidReferenceError( f"property '{ptr_name}' does not exist because" f" there are no '{ptr}' links between" f" {s_vn} and {t_vn}", context=ptr_expr.ptr.context, ) prefix_ptr_name = ( next(iter(ptrs)).get_shortname(ctx.env.schema).name) ptr = schemactx.get_union_pointer( ptrname=prefix_ptr_name, source=prefix_type, direction=ind_prefix.rptr.direction, components=ptrs, ctx=ctx, ) else: ptr = typegen.ptrcls_from_ptrref( path_tip.rptr.ptrref, ctx=ctx) if isinstance(ptr, s_links.Link): source = ptr else: raise errors.QueryError( 'improper reference to link property on ' 'a non-link object', context=step.context, ) assert isinstance(ptr, s_links.Link) else: source = get_set_type(path_tip, ctx=ctx) if isinstance(source, s_types.Tuple): path_tip = tuple_indirection_set( path_tip, source=source, ptr_name=ptr_name, source_context=step.context, ctx=ctx) else: path_tip = ptr_step_set( path_tip, source=source, ptr_name=ptr_name, direction=direction, ignore_computable=True, source_context=step.context, ctx=ctx) ptrcls = typegen.ptrcls_from_ptrref( path_tip.rptr.ptrref, ctx=ctx) if _is_computable_ptr(ptrcls, ctx=ctx): computables.append(path_tip) elif isinstance(step, qlast.TypeIntersection): arg_type = inference.infer_type(path_tip, ctx.env) if not isinstance(arg_type, s_objtypes.ObjectType): raise errors.QueryError( f'cannot apply type intersection operator to ' f'{arg_type.get_verbosename(ctx.env.schema)}: ' f'it is not an object type', context=step.context) if not isinstance(step.type, qlast.TypeName): raise errors.QueryError( f'complex type expressions are not supported here', context=step.context, ) typ = schemactx.get_schema_type(step.type.maintype, ctx=ctx) try: path_tip = type_intersection_set( path_tip, typ, optional=False, ctx=ctx) except errors.SchemaError as e: e.set_source_context(step.type.context) raise else: # Arbitrary expression if i > 0: # pragma: no cover raise RuntimeError( 'unexpected expression as a non-first path item') with ctx.newscope(fenced=True, temporary=True) as subctx: path_tip = ensure_set( dispatch.compile(step, ctx=subctx), ctx=subctx) if path_tip.path_id.is_type_intersection_path(): scope_set = path_tip.rptr.source else: scope_set = path_tip extra_scopes[scope_set] = context.ScopeInfo( path_scope=subctx.path_scope, tentative_work=[ cb for cb in subctx.tentative_work if cb not in ctx.tentative_work ], ) for key_path_id in path_tip.path_id.iter_weak_namespace_prefixes(): mapped = ctx.view_map.get(key_path_id) if mapped is not None: path_tip = new_set( path_id=mapped.path_id, stype=get_set_type(path_tip, ctx=ctx), expr=mapped.expr, rptr=mapped.rptr, ctx=ctx) break if pathctx.path_is_banned(path_tip.path_id, ctx=ctx): dname = stype.get_displayname(ctx.env.schema) raise errors.QueryError( f'invalid reference to {dname}: ' f'self-referencing INSERTs are not allowed', hint=( f'Use DETACHED if you meant to refer to an ' f'uncorrelated {dname} set' ), context=step.context, ) path_sets.append(path_tip) path_tip.context = expr.context # Since we are attaching the computable scopes as siblings to # the subpaths they're computing, we must make sure that the # actual path head is not visible from inside the computable scope. # # Example: # type Tree { # multi link children -> Tree; # parent := .<children[IS Tree]; # } # `SELECT Tree.parent` should generate rougly the following scope tree: # # (test::Tree).>parent[IS test::Tree]: { # "BRANCH": { # "(test::Tree)" # }, # "FENCE": { # "ns@(test::Tree).<children": { # "(test::Tree) 0x7f30c7885d90" # } # }, # } # # Note that we use an unfenced BRANCH node to isolate the path head, # to make sure it is still properly factorable. We temporarily flip # the branch to be a full fence for the compilation of the computable. fence_points = frozenset(c.path_id for c in computables) fences = pathctx.register_set_in_scope( path_tip, fence_points=fence_points, ctx=ctx, ) for fence in fences: fence.fenced = True for ir_set, scope_info in extra_scopes.items(): nodes = tuple( node for node in ctx.path_scope.find_descendants(ir_set.path_id) # if node.parent_fence not in fences ) if not nodes: # The path portion not being a descendant means # that is is already present in the scope above us, # along with the view scope. continue assert len(nodes) == 1 nodes[0].fuse_subtree(scope_info.path_scope.copy()) for cb in scope_info.tentative_work: stmtctx.at_stmt_fini(cb, ctx=ctx) scope_info.tentative_work[:] = [] if ir_set.path_scope_id is None: pathctx.assign_set_scope(ir_set, nodes[0], ctx=ctx) for ir_set in computables: scope = ctx.path_scope.find_descendant(ir_set.path_id) if scope is None: # The path is already in the scope, no point # in recompiling the computable expression. continue with ctx.new() as subctx: subctx.path_scope = scope comp_ir_set = computable_ptr_set(ir_set.rptr, ctx=subctx) i = path_sets.index(ir_set) if i != len(path_sets) - 1: path_sets[i + 1].rptr.source = comp_ir_set else: path_tip = comp_ir_set path_sets[i] = comp_ir_set for fence in fences: fence.fenced = False return path_tip
def get_exception(self, native_err, context, token=None): msg = native_err.args[0] hint = None if isinstance(native_err, errors.EdgeQLSyntaxError): return native_err else: if msg.startswith('Unexpected token: '): token = token or getattr(native_err, 'token', None) token_kind = token.kind() ltok = self.parser._stack[-1][0] # Look at the parsing stack and use tokens and # non-terminals to infer the parser rule when the # error occurred. i, rule = self._get_rule() if not token or token_kind == 'EOF': msg = 'Unexpected end of line' elif (rule == 'shape' and token_kind == 'IDENT' and isinstance(ltok, parsing.Nonterm)): # Make sure that the previous element in the stack # is some kind of Nonterminal, because if it's # not, this is probably not an issue of a missing # COMMA. hint = (f"It appears that a ',' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule == 'list of arguments' and # The stack is like <NodeName> LPAREN <AnyIdentifier> i == 1 and isinstance( ltok, (gr_exprs.AnyIdentifier, tokens.T_WITH, tokens.T_SELECT, tokens.T_FOR, tokens.T_INSERT, tokens.T_UPDATE, tokens.T_DELETE))): hint = ("Statement used as an expression must be " "enclosed in a set of parentheses") # We want the error context correspond to the # statement keyword context = ltok.context token = None elif (rule == 'array slice' and # The offending token was something that could # make an expression token_kind in {'IDENT', 'ICONST'} and not isinstance(ltok, tokens.T_COLON)): hint = (f"It appears that a ':' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule in {'list of arguments', 'tuple', 'array'} and # The offending token was something that could # make an expression token_kind in { 'IDENT', 'TRUE', 'FALSE', 'ICONST', 'FCONST', 'NICONST', 'NFCONST', 'BCONST', 'SCONST', } and not isinstance(ltok, tokens.T_COMMA)): hint = (f"It appears that a ',' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule == 'definition' and token_kind == 'IDENT'): # Something went wrong in a definition, so check # if the last successful token is a keyword. if (isinstance(ltok, gr_exprs.Identifier) and ltok.val.upper() == 'INDEX'): msg = (f"Expected 'ON', but got {token.text()!r} " f"instead") else: msg = f'Unexpected {token.text()!r}' elif rule == 'for iterator': msg = (f"missing '{{' before {token.text()!r} " f"in a FOR query") elif hasattr(token, 'val'): msg = f'Unexpected {token.val!r}' elif token_kind == 'NL': msg = 'Unexpected end of line' else: msg = f'Unexpected {token.text()!r}' return errors.EdgeQLSyntaxError(msg, hint=hint, context=context, token=token)
def ast_to_typeref(node: qlast.TypeName, *, metaclass: typing.Optional[so.ObjectMeta] = None, modaliases: typing.Dict[typing.Optional[str], str], schema) -> so.ObjectRef: if node.subtypes is not None and node.maintype.name == 'enum': from . import scalars as s_scalars return s_scalars.AnonymousEnumTypeRef( name='std::anyenum', elements=[st.val.value for st in node.subtypes], ) elif node.subtypes is not None: from . import types as s_types coll = s_types.Collection.get_class(node.maintype.name) if issubclass(coll, s_abc.Tuple): subtypes = collections.OrderedDict() # tuple declaration must either be named or unnamed, but not both named = None unnamed = None for si, st in enumerate(node.subtypes): if st.name: named = True type_name = st.name else: unnamed = True type_name = str(si) if named is not None and unnamed is not None: raise errors.EdgeQLSyntaxError( f'mixing named and unnamed tuple declaration ' f'is not supported', context=node.subtypes[0].context, ) subtypes[type_name] = ast_to_typeref(st, modaliases=modaliases, schema=schema) try: return coll.from_subtypes(schema, subtypes, {'named': bool(named)}) except errors.SchemaError as e: # all errors raised inside are pertaining to subtypes, so # the context should point to the first subtype e.set_source_context(node.subtypes[0].context) raise e else: subtypes = [] for st in node.subtypes: subtypes.append( ast_to_typeref(st, modaliases=modaliases, schema=schema)) try: return coll.from_subtypes(schema, subtypes) except errors.SchemaError as e: e.set_source_context(node.context) raise e elif isinstance(node.maintype, qlast.AnyType): from . import pseudo as s_pseudo return s_pseudo.AnyObjectRef() elif isinstance(node.maintype, qlast.AnyTuple): from . import pseudo as s_pseudo return s_pseudo.AnyTupleRef() return ast_objref_to_objref(node.maintype, modaliases=modaliases, metaclass=metaclass, schema=schema)
def ast_to_typeref(node: qlast.TypeName, *, metaclass: Optional[so.ObjectMeta] = None, modaliases: Mapping[Optional[str], str], schema: s_schema.Schema) -> so.Object: if node.subtypes is not None and isinstance(node.maintype, qlast.ObjectRef) \ and node.maintype.name == 'enum': from . import scalars as s_scalars return s_scalars.AnonymousEnumTypeRef( name='std::anyenum', elements=[ st.val.value for st in cast(List[qlast.TypeExprLiteral], node.subtypes) ], ) elif node.subtypes is not None: from . import types as s_types assert isinstance(node.maintype, qlast.ObjectRef) coll = s_types.Collection.get_class(node.maintype.name) if issubclass(coll, s_types.Tuple): # Note: if we used abc Tuple here, then we would need anyway # to assert it is an instance of s_types.Tuple to make mypy happy # (rightly so, because later we use from_subtypes method) subtypes: Dict[str, so.Object] \ = collections.OrderedDict() # tuple declaration must either be named or unnamed, but not both named = None unnamed = None for si, st in enumerate(node.subtypes): if st.name: named = True type_name = st.name else: unnamed = True type_name = str(si) if named is not None and unnamed is not None: raise errors.EdgeQLSyntaxError( f'mixing named and unnamed tuple declaration ' f'is not supported', context=node.subtypes[0].context, ) subtypes[type_name] = ast_to_typeref(cast(qlast.TypeName, st), modaliases=modaliases, metaclass=metaclass, schema=schema) try: return coll.from_subtypes( schema, cast(Mapping[str, s_types.Type], subtypes), {'named': bool(named)}) except errors.SchemaError as e: # all errors raised inside are pertaining to subtypes, so # the context should point to the first subtype e.set_source_context(node.subtypes[0].context) raise e else: subtypes_list: List[so.Object] = [] for st in node.subtypes: subtypes_list.append( ast_to_typeref(cast(qlast.TypeName, st), modaliases=modaliases, metaclass=metaclass, schema=schema)) try: return coll.from_subtypes( schema, cast(Sequence[s_types.Type], subtypes_list)) except errors.SchemaError as e: e.set_source_context(node.context) raise e elif isinstance(node.maintype, qlast.AnyType): from . import pseudo as s_pseudo return s_pseudo.AnyObjectRef() elif isinstance(node.maintype, qlast.AnyTuple): from . import pseudo as s_pseudo return s_pseudo.AnyTupleRef() assert isinstance(node.maintype, qlast.ObjectRef) return ast_objref_to_objref(node.maintype, modaliases=modaliases, metaclass=metaclass, schema=schema)
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_nodes.Node, *, path_id: irast.PathId, path_id_namespace: typing.Optional[irast.WeakNamespace] = None, is_insert: bool = False, is_update: bool = False, view_rptr: typing.Optional[context.ViewRPtr] = None, ctx: context.CompilerContext) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource = view_scls qlexpr = None if plen >= 2 and isinstance(steps[-1], qlast.TypeIndirection): # Target type indirection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] else: target_typexpr = None if plen == 1: # regular shape lexpr = steps[0] is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIndirection): # Source type indirection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type ptrsource = schemactx.get_schema_type(ptype.maintype, ctx=ctx) is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') ptrname = lexpr.ptr.name compexpr = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError("unexpected ':'", context=steps[-1].context) if compexpr is None: ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_view(ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.Name( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) if (shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIndirection(type=target_typexpr), ]) qlexpr = astutils.ensure_qlstmt(qlexpr) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit if target_typexpr is not None: ptr_target = schemactx.get_schema_type(target_typexpr.maintype, ctx=ctx) else: ptr_target = ptrcls.get_target(ctx.env.schema) if base_ptrcls in ctx.pending_cardinality: # We do not know the parent's pointer cardinality yet. ptr_cardinality = None ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) stmtctx.pend_pointer_cardinality_inference( ptrcls=ptrcls, specified_card=shape_el.cardinality, from_parent=True, source_ctx=shape_el.context, ctx=ctx) else: ptr_cardinality = base_ptrcls.get_cardinality(ctx.env.schema) implicit_tid = has_implicit_tid( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = pathctx.extend_path_id(path_id, ptrcls=base_ptrcls, target=ptrcls.get_target( ctx.env.schema), ns=ctx.path_id_namespace, ctx=ctx) ctx.path_scope.attach_path(sub_path_id) if is_update: for subel in shape_el.elements or []: is_prop = (isinstance(subel.expr.steps[0], qlast.Ptr) and subel.expr.steps[0].type == 'property') if not is_prop: raise errors.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view(stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, ctx=ctx) else: ptr_target = _process_view(stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, ctx=ctx) else: if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first( ctx.env.schema) ptr_name = sn.Name( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: # Otherwise, assume no pointer inheritance. # Every computable is a new pointer derived from # std::link or std::property. There is one exception: # pointer aliases (Foo {some := Foo.other}), where `foo` # gets derived from `Foo.other`. This logic is applied # in compile_query_subject() by populating the base_ptrcls. base_ptrcls = ptrcls = None ptr_name = sn.Name( module='__', name=ptrname, ) qlexpr = astutils.ensure_qlstmt(compexpr) with ctx.newscope(fenced=True) as shape_expr_ctx: # Put current pointer class in context, so # that references to link properties in sub-SELECT # can be resolved. This is necessary for proper # evaluation of link properties on computable links, # most importantly, in INSERT/UPDATE context. shape_expr_ctx.view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update) shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls, path_id=path_id, ctx=shape_expr_ctx) if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) if ptr_target.assignment_castable_to(base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not (base_target.is_object_type() or ptr_target.issubclass(ctx.env.schema, base_target)): qlexpr = astutils.ensure_qlstmt( qlast.TypeCast( type=astutils.type_to_ql_typeref( base_target, schema=ctx.env.schema), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})') if qlexpr is not None or ptrcls is None: if is_linkprop: src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object('std::link') else: base = ctx.env.get_track_schema_object('std::property') if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[src_scls.get_name(ctx.env.schema)], ctx=ctx) existing = ctx.env.schema.get(derived_name, None) if existing is not None: existing_target = existing.get_target(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to(existing_target, ctx.env.schema): ctx.env.schema = existing.set_target(ctx.env.schema, ptr_target) ptrcls = existing else: target_rptr_set = (ptr_target.get_rptr(ctx.env.schema) is not None) if target_rptr_set: ctx.env.schema = ptr_target.set_field_value( ctx.env.schema, 'rptr', None, ) ctx.env.schema = existing.delete(ctx.env.schema) ptrcls = schemactx.derive_view(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, inheritance_merge=False, ctx=ctx) if target_rptr_set: ctx.env.schema = ptr_target.set_field_value( ctx.env.schema, 'rptr', ptrcls, ) else: ptrcls = schemactx.derive_view(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'path_id_name', path_id_name) if qlexpr is not None: ctx.source_map[ptrcls] = (qlexpr, ctx, path_id, path_id_namespace) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'computable', True) if not is_mutation: if ptr_cardinality is None: if ptrcls not in ctx.pending_cardinality: if qlexpr is not None: from_parent = False elif ptrcls is not base_ptrcls: ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) from_parent = True else: from_parent = False stmtctx.pend_pointer_cardinality_inference( ptrcls=ptrcls, specified_card=shape_el.cardinality, from_parent=from_parent, source_ctx=shape_el.context, ctx=ctx) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality', None) else: ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality', ptr_cardinality) if ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) return ptrcls
def get_exception(self, native_err, context, token=None): msg = native_err.args[0] hint = None if isinstance(native_err, errors.EdgeQLSyntaxError): return native_err else: if msg.startswith('Unexpected token: '): token = token or getattr(native_err, 'token', None) token_kind = token.kind() ltok = self.parser._stack[-1][0] # Look at the parsing stack and use tokens and # non-terminals to infer the parser rule when the # error occurred. rule = '' # The last valid token was a closing # brace/parent/bracket, so we need to find a match for # it before deciding what rule context we're in. need_match = isinstance( ltok, (tokens.T_RBRACE, tokens.T_RPAREN, tokens.T_RBRACKET)) for i, (el, _) in enumerate(reversed(self.parser._stack)): if isinstance(el, tokens.Token): # We'll need the element right before "{", "[", or "(". prevel = self.parser._stack[-2 - i][0] if isinstance(el, tokens.T_LBRACE): if need_match and isinstance( ltok, tokens.T_RBRACE): # This is matched, while we're looking # for unmatched braces. need_match = False continue elif isinstance(prevel, gr_commondl.OptExtending): # This is some SDL/DDL rule = 'definition' elif ( isinstance(prevel, gr_exprs.Expr) or (isinstance(prevel, tokens.T_COLON) and isinstance(self.parser._stack[-3 - i][0], gr_exprs.ShapePointer))): # This is some kind of shape. rule = 'shape' break elif isinstance(el, tokens.T_LPAREN): if need_match and isinstance( ltok, tokens.T_RPAREN): # This is matched, while we're looking # for unmatched parentheses. need_match = False continue # This could be an argument list or a tuple. elif isinstance(prevel, gr_exprs.NodeName): rule = 'list of arguments' else: rule = 'tuple' break elif isinstance(el, tokens.T_LBRACKET): if need_match and isinstance( ltok, tokens.T_RBRACKET): # This is matched, while we're looking # for unmatched brackets. need_match = False continue # This is either an array literal or # array index. elif isinstance(prevel, gr_exprs.Expr): rule = 'array slice' else: rule = 'array' break if not token or token_kind == 'EOF': msg = 'Unexpected end of line' elif (rule == 'shape' and token_kind == 'IDENT' and isinstance(ltok, parsing.Nonterm)): # Make sure that the previous element in the stack # is some kind of Nonterminal, because if it's # not, this is probably not an issue of a missing # COMMA. hint = (f"It appears that a ',' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule == 'list of arguments' and # The stack is like <NodeName> LPAREN <AnyIdentifier> i == 1 and isinstance( ltok, (gr_exprs.AnyIdentifier, tokens.T_WITH, tokens.T_SELECT, tokens.T_FOR, tokens.T_INSERT, tokens.T_UPDATE, tokens.T_DELETE))): hint = ("Statement used as an expression must be " "enclosed in a set of parentheses") # We want the error context correspond to the # statement keyword context = ltok.context token = None elif (rule == 'array slice' and # The offending token was something that could # make an expression token_kind in {'IDENT', 'ICONST'} and not isinstance(ltok, tokens.T_COLON)): hint = (f"It appears that a ':' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule in {'list of arguments', 'tuple', 'array'} and # The offending token was something that could # make an expression token_kind in { 'IDENT', 'TRUE', 'FALSE', 'ICONST', 'FCONST', 'NICONST', 'NFCONST', 'BCONST', 'SCONST', } and not isinstance(ltok, tokens.T_COMMA)): hint = (f"It appears that a ',' is missing in {a(rule)} " f"before {token.text()!r}") elif (rule == 'definition' and token_kind == 'IDENT'): # Something went wrong in a definition, so check # if the last successful token is a keyword. if (isinstance(ltok, gr_exprs.Identifier) and ltok.val.upper() == 'INDEX'): msg = (f"Expected 'ON', but got {token.text()!r} " f"instead") else: msg = f'Unexpected {token.text()!r}' elif hasattr(token, 'val'): msg = f'Unexpected {token.val!r}' elif token_kind == 'NL': msg = 'Unexpected end of line' else: msg = f'Unexpected {token.text()!r}' return errors.EdgeQLSyntaxError(msg, hint=hint, context=context, token=token)
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_objtypes.ObjectType, *, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace]=None, is_insert: bool=False, is_update: bool=False, from_default: bool=False, view_rptr: Optional[context.ViewRPtr]=None, ctx: context.ContextLevel) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource: s_sources.Source = view_scls qlexpr: Optional[qlast.Expr] = None target_typexpr = None source: qlast.Base base_ptrcls_is_alias = False if plen >= 2 and isinstance(steps[-1], qlast.TypeIntersection): # Target type intersection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] if plen == 1: # regular shape lexpr = steps[0] assert isinstance(lexpr, qlast.Ptr) is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None or view_rptr.ptrcls is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) assert isinstance(view_rptr.ptrcls, s_links.Link) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection): # Source type intersection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type if not isinstance(ptype, qlast.TypeName): raise errors.QueryError( 'complex type expressions are not supported here', context=ptype.context, ) source_spec = schemactx.get_schema_type(ptype.maintype, ctx=ctx) if not isinstance(source_spec, s_objtypes.ObjectType): raise errors.QueryError( f'expected object type, got ' f'{source_spec.get_verbosename(ctx.env.schema)}', context=ptype.context, ) ptrsource = source_spec is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') assert isinstance(lexpr, qlast.Ptr) ptrname = lexpr.ptr.name compexpr: Optional[qlast.Expr] = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError( "unexpected ':'", context=steps[-1].context) ptrcls: Optional[s_pointers.Pointer] if compexpr is None: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_ptr( ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_is_singleton = False if base_cardinality is not None and base_cardinality.is_known(): base_is_singleton = base_cardinality.is_single() if ( shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None or (ctx.implicit_limit and not base_is_singleton) ): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIntersection(type=target_typexpr), ]) qlexpr = astutils.ensure_qlstmt(qlexpr) assert isinstance(qlexpr, qlast.SelectQuery) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby if shape_el.offset or shape_el.limit: qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True) qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit if ( (ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and not qlexpr.limit and ctx.implicit_limit and not base_is_singleton ): qlexpr.limit = qlast.IntegerConstant( value=str(ctx.implicit_limit), ) if target_typexpr is not None: assert isinstance(target_typexpr, qlast.TypeName) intersector_type = schemactx.get_schema_type( target_typexpr.maintype, ctx=ctx) int_result = schemactx.apply_intersection( ptrcls.get_target(ctx.env.schema), # type: ignore intersector_type, ctx=ctx, ) ptr_target = int_result.stype else: _ptr_target = ptrcls.get_target(ctx.env.schema) assert _ptr_target ptr_target = _ptr_target ptr_cardinality = base_cardinality if ptr_cardinality is None or not ptr_cardinality.is_known(): # We do not know the parent's pointer cardinality yet. ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) ctx.env.pointer_specified_info[ptrcls] = ( shape_el.cardinality, shape_el.required, shape_el.context) implicit_tid = has_implicit_type_computables( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = pathctx.extend_path_id( path_id, ptrcls=base_ptrcls, ns=ctx.path_id_namespace, ctx=ctx) ctx.path_scope.attach_path(sub_path_id, context=shape_el.context) if not isinstance(ptr_target, s_objtypes.ObjectType): raise errors.QueryError( f'shapes cannot be applied to ' f'{ptr_target.get_verbosename(ctx.env.schema)}', context=shape_el.context, ) if is_update: for subel in shape_el.elements or []: is_prop = ( isinstance(subel.expr.steps[0], qlast.Ptr) and subel.expr.steps[0].type == 'property' ) if not is_prop: raise errors.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, parser_context=shape_el.context, ctx=ctx) else: ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, parser_context=shape_el.context, ctx=ctx) else: base_ptrcls = ptrcls = None if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: ptr_name = sn.QualName( module='__', name=ptrname, ) try: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=False, ctx=ctx, ) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) except errors.InvalidReferenceError: # This is a NEW computable pointer, it's fine. pass qlexpr = astutils.ensure_qlstmt(compexpr) if ((ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit and isinstance(qlexpr, qlast.OffsetLimitMixin) and not qlexpr.limit): qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit)) with ctx.newscope(fenced=True) as shape_expr_ctx: # Put current pointer class in context, so # that references to link properties in sub-SELECT # can be resolved. This is necessary for proper # evaluation of link properties on computable links, # most importantly, in INSERT/UPDATE context. shape_expr_ctx.view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update, ) shape_expr_ctx.defining_view = view_scls shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls.get_bases(ctx.env.schema).first(ctx.env.schema), path_id=path_id, ctx=shape_expr_ctx) prefix_rptrref = path_id.rptr() if prefix_rptrref is not None: # Source path seems to contain multiple steps, # so set up a rptr for abbreviated link property # paths. src_path_id = path_id.src_path() assert src_path_id is not None ctx.env.schema, src_t = irtyputils.ir_typeref_to_type( shape_expr_ctx.env.schema, src_path_id.target, ) prefix_rptr = irast.Pointer( source=setgen.class_set( src_t, path_id=src_path_id, ctx=shape_expr_ctx, ), target=shape_expr_ctx.partial_path_prefix, ptrref=prefix_rptrref, direction=s_pointers.PointerDirection.Outbound, ) shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) shape_expr_ctx.stmt_metadata[qlexpr] = context.StatementMetadata( iterator_target=True, ) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) if ( shape_el.operation.op is qlast.ShapeOp.APPEND or shape_el.operation.op is qlast.ShapeOp.SUBTRACT ): if not is_update: op = ( '+=' if shape_el.operation.op is qlast.ShapeOp.APPEND else '-=' ) raise errors.EdgeQLSyntaxError( f"unexpected '{op}'", context=shape_el.operation.context, ) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls base_ptrcls_is_alias = shape_expr_ctx.view_rptr.ptrcls_is_alias if ptrcls is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True) ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) if ( isinstance(ptr_target, s_types.Collection) and not ctx.env.orig_schema.get_by_id(ptr_target.id, default=None) ): # Record references to implicitly defined collection types, # so that the alias delta machinery can pick them up. ctx.env.created_schema_objects.add(ptr_target) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) assert base_target is not None if ptr_target.assignment_castable_to( base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not ( base_target.is_object_type() or s_types.is_type_compatible( base_target, ptr_target, schema=ctx.env.schema ) ): qlexpr = astutils.ensure_qlstmt(qlast.TypeCast( type=typegen.type_to_ql_typeref(base_target, ctx=ctx), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] ercls: Type[errors.EdgeDBError] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})' ) if qlexpr is not None or ptrcls is None: src_scls: s_sources.Source if is_linkprop: # Proper checking was done when is_linkprop is defined. assert view_rptr is not None assert isinstance(view_rptr.ptrcls, s_links.Link) src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object( sn.QualName('std', 'link'), expr=None) else: base = ctx.env.get_track_schema_object( sn.QualName('std', 'property'), expr=None) if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[str(src_scls.get_name(ctx.env.schema))], ctx=ctx, ) existing = ctx.env.schema.get( derived_name, default=None, type=s_pointers.Pointer) if existing is not None: existing_target = existing.get_target(ctx.env.schema) assert existing_target is not None if ctx.recompiling_schema_alias: ptr_cardinality = existing.get_cardinality(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to( existing_target, ctx.env.schema): ctx.env.schema = existing.set_target( ctx.env.schema, ptr_target) ptrcls = existing else: vnp = existing.get_verbosename( ctx.env.schema, with_parent=True) t1_vn = existing_target.get_verbosename(ctx.env.schema) t2_vn = ptr_target.get_verbosename(ctx.env.schema) if compexpr is not None: source_context = compexpr.context else: source_context = shape_el.expr.steps[-1].context raise errors.SchemaError( f'cannot redefine {vnp} as {t2_vn}', details=f'{vnp} is defined as {t1_vn}', context=source_context, ) else: ptrcls = schemactx.derive_ptr( derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) assert ptrcls is not None if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'path_id_name', path_id_name ) if qlexpr is not None: ctx.source_map[ptrcls] = irast.ComputableInfo( qlexpr=qlexpr, context=ctx, path_id=path_id, path_id_ns=path_id_namespace, shape_op=shape_el.operation.op, ) if compexpr is not None or is_polymorphic: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'computable', True, ) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True, ) if ptr_cardinality is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', ptr_cardinality) else: if qlexpr is None and ptrcls is not base_ptrcls: ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) base_cardinality = None base_required = False if base_ptrcls is not None and not base_ptrcls_is_alias: base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_required = base_ptrcls.get_required(ctx.env.schema) if base_cardinality is None or not base_cardinality.is_known(): specified_cardinality = shape_el.cardinality specified_required = shape_el.required else: specified_cardinality = base_cardinality specified_required = base_required if (shape_el.cardinality is not None and base_ptrcls is not None and shape_el.cardinality != base_cardinality): base_src = base_ptrcls.get_source(ctx.env.schema) assert base_src is not None base_src_name = base_src.get_verbosename(ctx.env.schema) raise errors.SchemaError( f'cannot redefine the cardinality of ' f'{ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is defined as {base_cardinality.as_ptr_qual()!r} ' f'in the base {base_src_name}', context=compexpr and compexpr.context, ) # The required flag may be inherited from the base specified_required = shape_el.required or base_required ctx.env.pointer_specified_info[ptrcls] = ( specified_cardinality, specified_required, shape_el.context) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', qltypes.SchemaCardinality.Unknown) if ( ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None and not from_default and not ctx.env.options.allow_writing_protected_pointers ): ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) if is_update and ptrcls.get_readonly(ctx.env.schema): raise errors.QueryError( f'cannot update {ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is declared as read-only', context=compexpr and compexpr.context, ) return ptrcls
def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: """Create an ir.Set representing the given EdgeQL path expression.""" anchors = ctx.anchors if expr.partial: if ctx.partial_path_prefix is not None: path_tip = ctx.partial_path_prefix else: raise errors.QueryError('could not resolve partial path ', context=expr.context) computables = [] path_sets = [] for i, step in enumerate(expr.steps): if isinstance(step, qlast.SpecialAnchor): path_tip = resolve_special_anchor(step, ctx=ctx) elif isinstance(step, qlast.ObjectRef): if i > 0: # pragma: no cover raise RuntimeError( 'unexpected ObjectRef as a non-first path item') refnode = None if (not step.module and s_name.UnqualName(step.name) not in ctx.aliased_views): # Check if the starting path label is a known anchor refnode = anchors.get(step.name) if refnode is not None: path_tip = new_set_from_set(refnode, preserve_scope_ns=True, ctx=ctx) else: stype = schemactx.get_schema_type( step, condition=lambda o: (isinstance(o, s_types.Type) and (o.is_object_type() or o.is_view(ctx.env.schema))), label='object type or alias', item_type=s_types.QualifiedType, srcctx=step.context, ctx=ctx, ) if (stype.get_expr_type(ctx.env.schema) is not None and stype.get_name(ctx.env.schema) not in ctx.view_nodes): # This is a schema-level view, as opposed to # a WITH-block or inline alias view. stype = stmtctx.declare_view_from_schema(stype, ctx=ctx) view_set = ctx.view_sets.get(stype) if view_set is not None: view_scope_info = ctx.path_scope_map[view_set] path_tip = new_set_from_set( view_set, preserve_scope_ns=(view_scope_info.pinned_path_id_ns is not None), is_binding=True, ctx=ctx, ) else: path_tip = class_set(stype, ctx=ctx) view_scls = ctx.class_view_overrides.get(stype.id) if (view_scls is not None and view_scls != get_set_type(path_tip, ctx=ctx)): path_tip = ensure_set(path_tip, type_override=view_scls, ctx=ctx) elif isinstance(step, qlast.Ptr): # Pointer traversal step ptr_expr = step if ptr_expr.direction is not None: direction = s_pointers.PointerDirection(ptr_expr.direction) else: direction = s_pointers.PointerDirection.Outbound ptr_name = ptr_expr.ptr.name source: s_obj.Object ptr: s_pointers.PointerLike if ptr_expr.type == 'property': # Link property reference; the source is the # link immediately preceding this step in the path. if path_tip.rptr is None: raise errors.EdgeQLSyntaxError( f"unexpected reference to link property {ptr_name!r} " "outside of a path expression", context=ptr_expr.ptr.context, ) if isinstance(path_tip.rptr.ptrref, irast.TypeIntersectionPointerRef): ind_prefix, ptrs = typegen.collapse_type_intersection_rptr( path_tip, ctx=ctx, ) assert ind_prefix.rptr is not None prefix_type = get_set_type(ind_prefix.rptr.source, ctx=ctx) assert isinstance(prefix_type, s_objtypes.ObjectType) if not ptrs: tip_type = get_set_type(path_tip, ctx=ctx) s_vn = prefix_type.get_verbosename(ctx.env.schema) t_vn = tip_type.get_verbosename(ctx.env.schema) pn = ind_prefix.rptr.ptrref.shortname.name if direction is s_pointers.PointerDirection.Inbound: s_vn, t_vn = t_vn, s_vn raise errors.InvalidReferenceError( f"property '{ptr_name}' does not exist because" f" there are no '{pn}' links between" f" {s_vn} and {t_vn}", context=ptr_expr.ptr.context, ) prefix_ptr_name = (next(iter(ptrs)).get_local_name( ctx.env.schema)) ptr = schemactx.get_union_pointer( ptrname=prefix_ptr_name, source=prefix_type, direction=ind_prefix.rptr.direction, components=ptrs, ctx=ctx, ) else: ptr = typegen.ptrcls_from_ptrref(path_tip.rptr.ptrref, ctx=ctx) if isinstance(ptr, s_links.Link): source = ptr else: raise errors.QueryError( 'improper reference to link property on ' 'a non-link object', context=step.context, ) else: source = get_set_type(path_tip, ctx=ctx) # If this is followed by type intersections, collect # them up, since we need them in ptr_step_set. upcoming_intersections = [] for j in range(i + 1, len(expr.steps)): nstep = expr.steps[j] if (isinstance(nstep, qlast.TypeIntersection) and isinstance(nstep.type, qlast.TypeName)): upcoming_intersections.append( schemactx.get_schema_type(nstep.type.maintype, ctx=ctx)) else: break if isinstance(source, s_types.Tuple): path_tip = tuple_indirection_set(path_tip, source=source, ptr_name=ptr_name, source_context=step.context, ctx=ctx) else: path_tip = ptr_step_set( path_tip, expr=step, source=source, ptr_name=ptr_name, direction=direction, upcoming_intersections=upcoming_intersections, ignore_computable=True, source_context=step.context, ctx=ctx) assert path_tip.rptr is not None ptrcls = typegen.ptrcls_from_ptrref(path_tip.rptr.ptrref, ctx=ctx) if _is_computable_ptr(ptrcls, ctx=ctx): computables.append(path_tip) elif isinstance(step, qlast.TypeIntersection): arg_type = inference.infer_type(path_tip, ctx.env) if not isinstance(arg_type, s_objtypes.ObjectType): raise errors.QueryError( f'cannot apply type intersection operator to ' f'{arg_type.get_verbosename(ctx.env.schema)}: ' f'it is not an object type', context=step.context) if not isinstance(step.type, qlast.TypeName): raise errors.QueryError( f'complex type expressions are not supported here', context=step.context, ) typ = schemactx.get_schema_type(step.type.maintype, ctx=ctx) try: path_tip = type_intersection_set(path_tip, typ, optional=False, ctx=ctx) except errors.SchemaError as e: e.set_source_context(step.type.context) raise else: # Arbitrary expression if i > 0: # pragma: no cover raise RuntimeError( 'unexpected expression as a non-first path item') # We need to fence this if the head is a mutating # statement, to make sure that the factoring allowlist # works right. is_subquery = isinstance(step, qlast.Statement) with ctx.newscope(fenced=is_subquery) as subctx: path_tip = ensure_set(dispatch.compile(step, ctx=subctx), ctx=subctx) # If the head of the path is a direct object # reference, wrap it in an expression set to give it a # new path id. This prevents the object path from being # spuriously visible to computable paths defined in a shape # at the root of a path. (See test_edgeql_select_tvariant_04 # for an example). if (path_tip.path_id.is_objtype_path() and not path_tip.path_id.is_view_path() and path_tip.path_id.src_path() is None): path_tip = expression_set(ensure_stmt(path_tip, ctx=subctx), ctx=subctx) if path_tip.path_id.is_type_intersection_path(): assert path_tip.rptr is not None scope_set = path_tip.rptr.source else: scope_set = path_tip scope_set = scoped_set(scope_set, ctx=subctx) for key_path_id in path_tip.path_id.iter_weak_namespace_prefixes(): mapped = ctx.view_map.get(key_path_id) if mapped is not None: path_tip = new_set(path_id=mapped.path_id, stype=get_set_type(path_tip, ctx=ctx), expr=mapped.expr, rptr=mapped.rptr, ctx=ctx) break if pathctx.path_is_banned(path_tip.path_id, ctx=ctx): dname = stype.get_displayname(ctx.env.schema) raise errors.QueryError( f'invalid reference to {dname}: ' f'self-referencing INSERTs are not allowed', hint=(f'Use DETACHED if you meant to refer to an ' f'uncorrelated {dname} set'), context=step.context, ) path_sets.append(path_tip) path_tip.context = expr.context # Since we are attaching the computable scopes as siblings to # the subpaths they're computing, we must make sure that the # actual path head is not visible from inside the computable scope. # # Example: # type Tree { # multi link children -> Tree; # parent := .<children[IS Tree]; # } # `SELECT Tree.parent` should generate rougly the following scope tree: # # (test::Tree).>parent[IS test::Tree]: { # "BRANCH": { # "(test::Tree)" # }, # "FENCE": { # "ns@(test::Tree).<children": { # "(test::Tree) 0x7f30c7885d90" # } # }, # } # # Note that we use an unfenced BRANCH node to isolate the path head, # to make sure it is still properly factorable. # The branch insertion is handled automatically by attach_path, and # we temporarily flip the branch to be a full fence for the compilation # of the computable. fences = pathctx.register_set_in_scope( path_tip, ctx=ctx, ) for fence in fences: fence.fenced = True for ir_set in computables: scope = ctx.path_scope.find_descendant(ir_set.path_id) if scope is None: scope = ctx.path_scope.find_visible(ir_set.path_id) # We skip recompiling if we can't find a scope for it. # This whole mechanism seems a little sketchy, unfortunately. if scope is None: continue with ctx.new() as subctx: subctx.path_scope = scope assert ir_set.rptr is not None comp_ir_set = computable_ptr_set(ir_set.rptr, ctx=subctx) i = path_sets.index(ir_set) if i != len(path_sets) - 1: prptr = path_sets[i + 1].rptr assert prptr is not None prptr.source = comp_ir_set else: path_tip = comp_ir_set path_sets[i] = comp_ir_set for fence in fences: fence.fenced = False return path_tip
def ast_to_type_shell( node: qlast.TypeName, *, metaclass: Optional[Type[s_types.Type]] = None, modaliases: Mapping[Optional[str], str], schema: s_schema.Schema, ) -> s_types.TypeShell: if (node.subtypes is not None and isinstance(node.maintype, qlast.ObjectRef) and node.maintype.name == 'enum'): from . import scalars as s_scalars return s_scalars.AnonymousEnumTypeShell(elements=[ st.val.value for st in cast(List[qlast.TypeExprLiteral], node.subtypes) ], ) elif node.subtypes is not None: from . import types as s_types assert isinstance(node.maintype, qlast.ObjectRef) coll = s_types.Collection.get_class(node.maintype.name) if issubclass(coll, s_types.Tuple): # Note: if we used abc Tuple here, then we would need anyway # to assert it is an instance of s_types.Tuple to make mypy happy # (rightly so, because later we use from_subtypes method) subtypes: Dict[str, s_types.TypeShell] = {} # tuple declaration must either be named or unnamed, but not both named = None unnamed = None for si, st in enumerate(node.subtypes): if st.name: named = True type_name = st.name else: unnamed = True type_name = str(si) if named is not None and unnamed is not None: raise errors.EdgeQLSyntaxError( f'mixing named and unnamed tuple declaration ' f'is not supported', context=node.subtypes[0].context, ) subtypes[type_name] = ast_to_type_shell( cast(qlast.TypeName, st), modaliases=modaliases, metaclass=metaclass, schema=schema, ) try: return coll.create_shell( schema, subtypes=subtypes, typemods={'named': bool(named)}, ) except errors.SchemaError as e: # all errors raised inside are pertaining to subtypes, so # the context should point to the first subtype e.set_source_context(node.subtypes[0].context) raise e elif issubclass(coll, s_types.Array): subtypes_list: List[s_types.TypeShell] = [] for st in node.subtypes: subtypes_list.append( ast_to_type_shell( cast(qlast.TypeName, st), modaliases=modaliases, metaclass=metaclass, schema=schema, )) if len(subtypes_list) != 1: raise errors.SchemaError( f'unexpected number of subtypes,' f' expecting 1, got {len(subtypes_list)}', context=node.context, ) if isinstance(subtypes_list[0], s_types.ArrayTypeShell): raise errors.UnsupportedFeatureError( 'nested arrays are not supported', context=node.subtypes[0].context, ) try: return coll.create_shell( schema, subtypes=subtypes_list, ) except errors.SchemaError as e: e.set_source_context(node.context) raise e elif isinstance(node.maintype, qlast.AnyType): from . import pseudo as s_pseudo return s_pseudo.PseudoTypeShell(name='anytype') elif isinstance(node.maintype, qlast.AnyTuple): from . import pseudo as s_pseudo return s_pseudo.PseudoTypeShell(name='anytuple') assert isinstance(node.maintype, qlast.ObjectRef) return ast_objref_to_type_shell( node.maintype, modaliases=modaliases, metaclass=metaclass, schema=schema, )