Пример #1
0
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:
                    path_scope, path_scope_ns = ctx.path_scope_map[view_set]
                    path_tip = new_set_from_set(
                        view_set,
                        preserve_scope_ns=path_scope_ns is not None,
                        ctx=ctx,
                    )
                    extra_scopes[path_tip] = path_scope.copy()
                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 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)

                    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)

            with ctx.newscope(fenced=True, temporary=True) as subctx:
                if isinstance(source, s_abc.Tuple):
                    path_tip = tuple_indirection_set(
                        path_tip, source=source, ptr_name=ptr_name,
                        source_context=step.context, ctx=subctx)

                else:
                    path_tip = ptr_step_set(
                        path_tip, source=source, ptr_name=ptr_name,
                        direction=direction,
                        ignore_computable=True,
                        source_context=step.context, ctx=subctx)

                    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] = subctx.path_scope

        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 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 ir_set, scope 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)
        if ir_set.path_scope_id is None:
            pathctx.assign_set_scope(ir_set, nodes[0], ctx=ctx)

    for fence in fences:
        fence.fenced = False

    return path_tip
Пример #2
0
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
Пример #3
0
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:
                    (o.is_object_type() or o.is_view(ctx.env.schema)),
                    label='object type or view',
                    srcctx=step.context,
                    ctx=ctx,
                )

                if (stype.get_view_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:
                    path_tip = new_set_from_set(view_set, ctx=ctx)
                    path_scope = ctx.path_scope_map[view_set]
                    extra_scopes[path_tip] = path_scope.copy()
                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: typing.Union[s_types.Type, s_pointers.PointerLike]

            if ptr_expr.type == 'property':
                # Link property reference; the source is the
                # link immediately preceding this step in the path.
                source = irtyputils.ptrcls_from_ptrref(path_tip.rptr.ptrref,
                                                       schema=ctx.env.schema)
            else:
                source = get_set_type(path_tip, ctx=ctx)

            with ctx.newscope(fenced=True, temporary=True) as subctx:
                if isinstance(source, s_abc.Tuple):
                    path_tip = tuple_indirection_set(
                        path_tip,
                        source=source,
                        ptr_name=ptr_name,
                        source_context=step.context,
                        ctx=subctx)

                else:
                    path_tip = ptr_step_set(path_tip,
                                            source=source,
                                            ptr_name=ptr_name,
                                            direction=direction,
                                            ignore_computable=True,
                                            source_context=step.context,
                                            ctx=subctx)

                    ptrcls = irtyputils.ptrcls_from_ptrref(
                        path_tip.rptr.ptrref, schema=ctx.env.schema)
                    if _is_computable_ptr(ptrcls, ctx=ctx):
                        computables.append(path_tip)

        elif isinstance(step, qlast.TypeIndirection):
            arg_type = inference.infer_type(path_tip, ctx.env)
            if not isinstance(arg_type, s_objtypes.ObjectType):
                raise errors.QueryError(
                    f'invalid type filter operand: '
                    f'{arg_type.get_displayname(ctx.env.schema)} '
                    f'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)
            if not isinstance(typ, s_objtypes.ObjectType):
                raise errors.QueryError(
                    f'invalid type filter operand: '
                    f'{typ.get_displayname(ctx.env.schema)} is not '
                    f'an object type',
                    context=step.type.context)

            # The expression already of the desired type, elide
            # the indirection.
            if arg_type != typ:
                path_tip = class_indirection_set(path_tip,
                                                 typ,
                                                 optional=False,
                                                 ctx=ctx)

        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_indirection_path():
                    scope_set = path_tip.rptr.source
                else:
                    scope_set = path_tip

                extra_scopes[scope_set] = subctx.path_scope

        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
    pathctx.register_set_in_scope(path_tip, 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 ir_set, scope in extra_scopes.items():
        node = ctx.path_scope.find_descendant(ir_set.path_id)
        if node is None:
            # The path portion not being a descendant means
            # that is is already present in the scope above us,
            # along with the view scope.
            continue

        fuse_scope_branch(ir_set, node, scope, ctx=ctx)
        if ir_set.path_scope_id is None:
            pathctx.assign_set_scope(ir_set, node, ctx=ctx)

    return path_tip