Exemplo n.º 1
0
def _get_hard_deps(
    expr: qlast.TypeExpr,
    *,
    ctx: DepTraceContext
) -> MutableSet[s_name.QualName]:
    deps: MutableSet[s_name.QualName] = set()

    # If we have any type ops, get a flat list of their operands.
    targets = qlast.get_targets(expr)
    for target in targets:
        # We care about subtypes dependencies, because
        # they can either be custom scalars or illegal
        # ObjectTypes (then error message will depend on
        # dependency tracing)
        if target.subtypes:
            for subtype in target.subtypes:
                # Recurse!
                deps |= _get_hard_deps(subtype, ctx=ctx)

        else:
            # Base case.
            name = ctx.get_ref_name(target.maintype)
            if name.get_module_name() not in s_schema.STD_MODULES:
                deps.add(name)

    return deps
Exemplo n.º 2
0
    def _process_create_or_alter_ast(self, schema, astnode, context):
        """Handle the CREATE {PROPERTY|LINK} AST node.

        This may be called in the context of either Create or Alter.
        """
        if astnode.is_required is not None:
            self.set_attribute_value('required', astnode.is_required)

        if astnode.cardinality is not None:
            self.set_attribute_value('cardinality', astnode.cardinality)

        parent_ctx = self.get_referrer_context(context)
        source_name = parent_ctx.op.classname
        self.set_attribute_value('source', so.ObjectRef(name=source_name))

        # FIXME: this is an approximate solution
        targets = qlast.get_targets(astnode.target)

        if len(targets) > 1:
            new_targets = [
                utils.ast_to_typeref(t,
                                     modaliases=context.modaliases,
                                     schema=schema,
                                     metaclass=s_types.Type) for t in targets
            ]

            target_ref = s_types.UnionTypeRef(new_targets,
                                              module=source_name.module)
        elif targets:
            target_expr = targets[0]
            if isinstance(target_expr, qlast.TypeName):
                target_ref = utils.ast_to_typeref(
                    target_expr,
                    modaliases=context.modaliases,
                    schema=schema,
                    metaclass=s_types.Type)
            else:
                # computable
                target_ref = ComputableRef(
                    s_expr.imprint_expr_context(
                        target_expr,
                        context.modaliases,
                    ))
        else:
            # Target is inherited.
            target_ref = None

        if isinstance(self, sd.CreateObject):
            self.set_attribute_value('target',
                                     target_ref,
                                     source_context=astnode.target.context)

            if self.get_attribute_value('cardinality') is None:
                self.set_attribute_value('cardinality',
                                         qltypes.Cardinality.ONE)

            if self.get_attribute_value('required') is None:
                self.set_attribute_value('required', False)
        elif target_ref is not None:
            self._set_pointer_type(schema, astnode, context, target_ref)
Exemplo n.º 3
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        targets = qlast.get_targets(astnode.type)
        alter_ptr_ctx = context.get(PointerCommandContext)
        alter_ptr_op = alter_ptr_ctx.op

        if len(targets) > 1:
            new_targets = [
                utils.ast_to_typeref(
                    t, modaliases=context.modaliases,
                    schema=schema)
                for t in targets
            ]

            target = alter_ptr_op._create_union_target(
                schema, context, new_targets, module=cmd.classname.module)

            target_ref = utils.reduce_to_typeref(schema, target)
        else:
            target = targets[0]
            target_ref = utils.ast_to_typeref(
                target, modaliases=context.modaliases, schema=schema)

            target_obj = utils.resolve_typeref(target_ref, schema=schema)
            if target_obj.is_collection():
                sd.ensure_schema_collection(
                    schema, target_obj, alter_ptr_ctx.op,
                    src_context=astnode.type.context,
                    context=context,
                )

        cmd.set_attribute_value('target', target_ref)

        return cmd
Exemplo n.º 4
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        targets = qlast.get_targets(astnode.type)

        if len(targets) > 1:
            new_targets = [
                utils.ast_to_type_shell(
                    t,
                    modaliases=context.modaliases,
                    schema=schema,
                ) for t in targets
            ]

            target_ref = s_types.UnionTypeShell(
                new_targets,
                module=cls.classname.module,
            )
        else:
            target = targets[0]
            target_ref = utils.ast_to_type_shell(
                target,
                modaliases=context.modaliases,
                schema=schema,
            )

        cmd.set_attribute_value('target', target_ref)

        return cmd
Exemplo n.º 5
0
    def _cmd_tree_from_ast(
        cls,
        schema: s_schema.Schema,
        astnode: qlast.DDLOperation,
        context: sd.CommandContext,
    ) -> sd.Command:
        assert isinstance(astnode, qlast.SetPointerType)
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        targets = qlast.get_targets(astnode.type)
        target_ref: s_types.TypeShell

        if len(targets) > 1:
            new_targets = [
                utils.ast_to_type_shell(
                    t,
                    modaliases=context.modaliases,
                    schema=schema,
                ) for t in targets
            ]

            target_ref = s_types.UnionTypeShell(
                new_targets,
                module=cls.classname.module,
            )
        else:
            target = targets[0]
            target_ref = utils.ast_to_type_shell(
                target,
                modaliases=context.modaliases,
                schema=schema,
            )

        if isinstance(target_ref, s_types.CollectionTypeShell):
            s_types.ensure_schema_collection(
                schema,
                target_ref,
                parent_cmd=cmd,
                src_context=astnode.type.context,
                context=context,
            )

        ctx = context.current()
        assert isinstance(ctx, sd.ObjectCommandContext)
        ptr = ctx.op.get_object(schema, context, default=None)  # type: ignore
        if ptr is not None:
            orig_type = ptr.get_target(schema)
            if orig_type.is_collection():
                s_types.cleanup_schema_collection(schema,
                                                  orig_type,
                                                  ptr,
                                                  cmd,
                                                  context=context,
                                                  src_context=astnode.context)

        cmd.set_attribute_value('target', target_ref)

        return cmd
Exemplo n.º 6
0
 def reduce_CreateRegularLink(self, *kids):
     """%reduce
         LINK ShortNodeName OptExtendingSimple
         ARROW FullTypeExpr CreateConcreteLinkSDLCommandsBlock
     """
     self.val = qlast.Link(name=kids[1].val.name,
                           extends=kids[2].val,
                           target=qlast.get_targets(kids[4].val),
                           **_process_commands(kids[5].val))
Exemplo n.º 7
0
 def reduce_CreateRegularProperty(self, *kids):
     """%reduce
         PROPERTY ShortNodeName OptExtendingSimple
         ARROW FullTypeExpr
     """
     self.val = qlast.Property(
         name=kids[1].val.name,
         extends=kids[2].val,
         target=qlast.get_targets(kids[4].val),
     )
Exemplo n.º 8
0
 def reduce_CreateRegularLink(self, *kids):
     """%reduce
         LINK ShortNodeName OptExtendingSimple
         ARROW FullTypeExpr
     """
     self.val = qlast.Link(
         name=kids[1].val.name,
         extends=kids[2].val,
         target=qlast.get_targets(kids[4].val),
     )
Exemplo n.º 9
0
 def reduce_CreateQualifiedRegularLink(self, *kids):
     """%reduce
         PtrQuals LINK ShortNodeName OptExtendingSimple
         ARROW FullTypeExpr CreateConcreteLinkSDLCommandsBlock
     """
     self.val = qlast.Link(inherited=kids[0].val.inherited,
                           required=kids[0].val.required,
                           cardinality=kids[0].val.cardinality,
                           name=kids[2].val.name,
                           extends=kids[3].val,
                           target=qlast.get_targets(kids[5].val),
                           **_process_commands(kids[6].val))
Exemplo n.º 10
0
 def reduce_CreateQualifiedRegularProperty(self, *kids):
     """%reduce
         PtrQuals PROPERTY ShortNodeName OptExtendingSimple
         ARROW FullTypeExpr
     """
     self.val = qlast.Property(
         name=kids[2].val.name,
         extends=kids[3].val,
         inherited=kids[0].val.inherited,
         required=kids[0].val.required,
         cardinality=kids[0].val.cardinality,
         target=qlast.get_targets(kids[5].val),
     )
Exemplo n.º 11
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        targets = qlast.get_targets(astnode.target)

        if len(targets) > 1:
            alter_ptr_ctx = context.get(pointers.PointerCommandContext)
            new_targets = [
                utils.ast_to_typeref(t,
                                     modaliases=context.modaliases,
                                     schema=schema) for t in targets
            ]

            alter_ptr_ctx.op.add(
                sd.AlterObjectProperty(
                    property='spectargets',
                    new_value=so.ObjectList.create(schema, new_targets),
                ))

            target = cls._create_union_target(schema,
                                              context,
                                              new_targets,
                                              module=cmd.classname.module)
        else:
            target = targets[0]
            target_ref = utils.ast_to_typeref(target,
                                              modaliases=context.modaliases,
                                              schema=schema)

            target_obj = utils.resolve_typeref(target_ref, schema=schema)
            if target_obj.is_collection():
                sd.ensure_schema_collection(
                    schema,
                    target_obj,
                    alter_ptr_ctx.op,
                    src_context=astnode.target.context,
                    context=context,
                )

        cmd.new_value = target_ref

        return cmd
Exemplo n.º 12
0
    def _cmd_tree_from_ast(
        cls,
        schema: s_schema.Schema,
        astnode: qlast.DDLOperation,
        context: sd.CommandContext,
    ) -> sd.Command:
        assert isinstance(astnode, qlast.SetPointerType)
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        targets = qlast.get_targets(astnode.type)
        target_ref: s_types.TypeShell

        if len(targets) > 1:
            new_targets = [
                utils.ast_to_type_shell(
                    t,
                    modaliases=context.modaliases,
                    schema=schema,
                )
                for t in targets
            ]

            target_ref = s_types.UnionTypeShell(
                new_targets,
                module=cls.classname.module,
            )
        else:
            target = targets[0]
            target_ref = utils.ast_to_type_shell(
                target,
                modaliases=context.modaliases,
                schema=schema,
            )

        cmd.set_attribute_value('target', target_ref)

        return cmd
Exemplo n.º 13
0
    def _process_create_or_alter_ast(
        self,
        schema: s_schema.Schema,
        astnode: qlast.CreateConcretePointer,
        context: sd.CommandContext,
    ) -> None:
        """Handle the CREATE {PROPERTY|LINK} AST node.

        This may be called in the context of either Create or Alter.
        """
        if astnode.is_required is not None:
            self.set_attribute_value('required', astnode.is_required)

        if astnode.cardinality is not None:
            self.set_attribute_value('cardinality', astnode.cardinality)

        parent_ctx = self.get_referrer_context_or_die(context)
        source_name = parent_ctx.op.classname
        self.set_attribute_value('source', so.ObjectShell(name=source_name))

        # FIXME: this is an approximate solution
        targets = qlast.get_targets(astnode.target)
        target_ref: Union[None, s_types.TypeShell, ComputableRef]

        if len(targets) > 1:
            assert isinstance(source_name, sn.Name)

            new_targets = [
                utils.ast_to_type_shell(
                    t,
                    modaliases=context.modaliases,
                    schema=schema,
                ) for t in targets
            ]

            target_ref = s_types.UnionTypeShell(
                new_targets,
                module=source_name.module,
            )
        elif targets:
            target_expr = targets[0]
            if isinstance(target_expr, qlast.TypeName):
                target_ref = utils.ast_to_type_shell(
                    target_expr,
                    modaliases=context.modaliases,
                    schema=schema,
                )
            else:
                # computable
                target_ref = ComputableRef(
                    s_expr.imprint_expr_context(
                        target_expr,
                        context.modaliases,
                    ))
        else:
            # Target is inherited.
            target_ref = None

        if isinstance(target_ref, s_types.CollectionTypeShell):
            assert astnode.target is not None
            s_types.ensure_schema_collection(
                schema,
                target_ref,
                parent_cmd=self,
                src_context=astnode.target.context,
                context=context,
            )

        if isinstance(self, sd.CreateObject):
            assert astnode.target is not None
            self.set_attribute_value(
                'target',
                target_ref,
                source_context=astnode.target.context,
            )

            # If target is a computable ref defer cardinality
            # enforcement until the expression is parsed.
            if not isinstance(target_ref, ComputableRef):
                if self.get_attribute_value('cardinality') is None:
                    self.set_attribute_value('cardinality',
                                             qltypes.SchemaCardinality.ONE)

                if self.get_attribute_value('required') is None:
                    self.set_attribute_value('required', False)

        elif target_ref is not None:
            self._set_pointer_type(schema, astnode, context, target_ref)
Exemplo n.º 14
0
    def _process_link_create_or_alter(cls, schema, astnode, context, cmd):
        from . import objtypes as s_objtypes

        parent_ctx = context.get(LinkSourceCommandContext)

        if isinstance(astnode, qlast.CreateConcreteLink):
            # "source" attribute is set automatically as a refdict back-attr
            source_name = parent_ctx.op.classname

            if astnode.is_required is not None:
                cmd.set_attribute_value('required', astnode.is_required)

            if astnode.cardinality is not None:
                cmd.set_attribute_value('cardinality', astnode.cardinality)

            # FIXME: this is an approximate solution
            targets = qlast.get_targets(astnode.target)

            if len(targets) > 1:
                new_targets = [
                    utils.ast_to_typeref(
                        t, modaliases=context.modaliases,
                        schema=schema)
                    for t in targets
                ]

                target = cls._create_union_target(
                    schema, context, new_targets, module=source_name.module)
            else:
                target_expr = targets[0]
                if isinstance(target_expr, qlast.TypeName):
                    target = utils.ast_to_typeref(
                        target_expr, modaliases=context.modaliases,
                        schema=schema)
                else:
                    # computable
                    target, base = cmd._parse_computable(
                        target_expr, schema, context)

                    if base is not None:
                        cmd.set_attribute_value(
                            'bases', so.ObjectList.create(schema, [base]),
                        )

                        cmd.set_attribute_value(
                            'is_derived', True
                        )

                        if context.declarative:
                            cmd.set_attribute_value(
                                'declared_inherited', True
                            )

                if (isinstance(target, so.ObjectRef) and
                        target.name == source_name):
                    # Special case for loop links.  Since the target
                    # is the same as the source, we know it's a proper
                    # type.
                    pass
                else:
                    target_type = utils.resolve_typeref(target, schema=schema)
                    if not isinstance(target_type, s_objtypes.ObjectType):
                        raise errors.InvalidLinkTargetError(
                            f'invalid link target, expected object type, got '
                            f'{target_type.__class__.__name__}',
                            context=astnode.target.context
                        )

            if isinstance(cmd, sd.CreateObject):
                cmd.set_attribute_value('target', target)

                if cmd.get_attribute_value('cardinality') is None:
                    cmd.set_attribute_value(
                        'cardinality', qltypes.Cardinality.ONE)

                if cmd.get_attribute_value('required') is None:
                    cmd.set_attribute_value(
                        'required', False)
            else:
                slt = SetLinkType(classname=cmd.classname, type=target)
                slt.set_attribute_value('target', target)
                cmd.add(slt)

            cls._parse_default(cmd)

        if parent_ctx is None:
            # this is an abstract link then
            if cmd.get_attribute_value('default') is not None:
                raise errors.SchemaDefinitionError(
                    f"'default' is not a valid field for an abstact link",
                    context=astnode.context)
Exemplo n.º 15
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        from . import objtypes as s_objtypes

        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        parent_ctx = context.get(LinkSourceCommandContext)
        source_name = parent_ctx.op.classname

        alter_ptr_ctx = context.get(pointers.PointerCommandContext)

        targets = qlast.get_targets(astnode.target)

        if len(targets) > 1:

            alter_ptr_ctx.op.add(
                sd.AlterObjectProperty(
                    property='spectargets',
                    new_value=so.ObjectList([
                        so.ObjectRef(
                            name=sn.Name(module=t.module, name=t.name))
                        for t in targets
                    ])))

            target_name = sources.Source.gen_virt_parent_name(
                (sn.Name(module=t.module, name=t.name) for t in targets),
                module=source_name.module)

            target_ref = so.ObjectRef(name=target_name)

            create_virt_parent = s_objtypes.CreateObjectType(
                classname=target_name, metaclass=s_objtypes.ObjectType)

            create_virt_parent.update(
                (sd.AlterObjectProperty(property='name',
                                        new_value=target_name),
                 sd.AlterObjectProperty(property='is_virtual', new_value=True),
                 sd.AlterObjectProperty(property='is_derived',
                                        new_value=True)))

            delta_ctx = context.get(sd.DeltaRootContext)

            for cc in delta_ctx.op(s_objtypes.CreateObjectType):
                if cc.classname == create_virt_parent.classname:
                    break
            else:
                delta_ctx.op.add(create_virt_parent)
        else:
            target = targets[0]
            target_ref = utils.ast_to_typeref(target,
                                              modaliases=context.modaliases,
                                              schema=schema)

            target_obj = utils.resolve_typeref(target_ref, schema=schema)
            if target_obj.is_collection():
                sd.ensure_schema_collection(
                    schema,
                    target_obj,
                    alter_ptr_ctx.op,
                    src_context=astnode.target.context,
                    context=context,
                )

        cmd.new_value = target_ref

        return cmd
Exemplo n.º 16
0
def _register_item(decl,
                   *,
                   deps=None,
                   hard_dep_exprs=None,
                   loop_control=None,
                   source=None,
                   subject=None,
                   extra_name=None,
                   ctx):

    if deps:
        deps = set(deps)
    else:
        deps = set()

    op = orig_op = copy.copy(decl)

    if isinstance(decl, qlast.CreateConcretePointer):
        name = decl.name.name
    else:
        name = ctx.get_local_name(decl.name)

    if ctx.depstack:
        op.alter_if_exists = True
        top_parent = parent = copy.copy(ctx.depstack[0][0])
        parent.commands = []
        for entry, _entry_name in ctx.depstack[1:]:
            entry_op = copy.copy(entry)
            entry_op.commands = []
            parent.commands.append(entry_op)
            parent = entry_op

        parent.commands.append(op)
        op = top_parent

        fq_name = ctx.depstack[-1][1] + "@" + name
    else:
        op.aliases = [qlast.ModuleAliasDecl(alias=None, module=ctx.module)]
        fq_name = name

    if extra_name is not None:
        fq_name = f'{fq_name}:{extra_name}'

    node = {
        "item": op,
        "deps": {n
                 for _, n in ctx.depstack if n != loop_control},
    }
    ctx.ddlgraph[fq_name] = node

    if hasattr(decl, "bases"):
        bases = set()

        for ref in _get_bases(decl, ctx=ctx):
            if ref.module == ctx.module:
                bases.add(ref)

        deps.update(bases)
        ctx.inhgraph[fq_name] = bases

    if ctx.depstack:
        parent_bases = ctx.inhgraph.get(ctx.depstack[-1][1])
        if parent_bases:
            for parent_base in parent_bases:
                base_item = f'{parent_base}@{name}'
                if base_item in ctx.objects:
                    deps.add(base_item)

    ast_subcommands = getattr(decl, 'commands', [])
    commands = []
    if ast_subcommands:
        subcmds = []
        for cmd in ast_subcommands:
            if isinstance(cmd, qlast.ObjectDDL):
                subcmds.append(cmd)
            elif (isinstance(cmd, qlast.SetField)
                  and not isinstance(cmd.value, qlast.BaseConstant)
                  and not isinstance(op, qlast.CreateView)):
                subcmds.append(cmd)
            else:
                commands.append(cmd)

        if subcmds:
            alter_name = f"Alter{decl.__class__.__name__[len('Create'):]}"
            alter_cls = getattr(qlast, alter_name)
            alter_cmd = alter_cls(name=decl.name)

            # indexes need to preserve their "on" expression
            if alter_name == 'AlterIndex':
                # find the original expr, which will be in non-normalized form
                for sub in op.commands:
                    if isinstance(sub, qlast.CreateIndex):
                        alter_cmd.expr = sub.expr
                        break
            # constraints need to preserve their "on" expression
            elif alter_name == 'AlterConcreteConstraint':
                # find the original expr, which will be in non-normalized form
                for sub in op.commands:
                    if isinstance(sub, qlast.CreateConcreteConstraint):
                        alter_cmd.subjectexpr = sub.subjectexpr
                        break

            if not ctx.depstack:
                alter_cmd.aliases = [
                    qlast.ModuleAliasDecl(alias=None, module=ctx.module)
                ]

            ctx.depstack.append((alter_cmd, fq_name))

            for cmd in subcmds:
                trace_dependencies(cmd, ctx=ctx)

            ctx.depstack.pop()

    if hard_dep_exprs:
        for expr in hard_dep_exprs:
            if isinstance(expr, qlast.TypeExpr):
                targets = qlast.get_targets(expr)
                for target in targets:
                    if not target.subtypes:
                        name = ctx.get_ref_name(target.maintype)
                        if name.module == ctx.module:
                            deps.add(name)
            else:
                for dep in qltracer.trace_refs(
                        expr,
                        schema=ctx.schema,
                        module=ctx.module,
                        source=source,
                        path_prefix=source,
                        subject=subject or fq_name,
                        objects=ctx.objects,
                ):
                    if dep.startswith(f"{ctx.module}::"):
                        deps.add(dep)

    orig_op.commands = commands

    if loop_control:
        parent_node = ctx.ddlgraph[loop_control]
        if 'loop-control' not in parent_node:
            parent_node['loop-control'] = {fq_name}
        else:
            parent_node['loop-control'].add(fq_name)

    node["deps"].update(deps)
Exemplo n.º 17
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        from . import objtypes as s_objtypes

        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        if isinstance(astnode, qlast.CreateConcreteLink):
            cmd.add(
                sd.AlterObjectProperty(property='required',
                                       new_value=astnode.is_required))

            cmd.add(
                sd.AlterObjectProperty(property='cardinality',
                                       new_value=astnode.cardinality))

            # "source" attribute is set automatically as a refdict back-attr
            parent_ctx = context.get(LinkSourceCommandContext)
            source_name = parent_ctx.op.classname
            target_type = None

            # FIXME: this is an approximate solution
            targets = qlast.get_targets(astnode.target)

            if len(targets) > 1:
                cmd.add(
                    sd.AlterObjectProperty(
                        property='spectargets',
                        new_value=so.ObjectList([
                            utils.ast_to_typeref(t,
                                                 modaliases=context.modaliases,
                                                 schema=schema)
                            for t in targets
                        ])))

                target_name = sources.Source.gen_virt_parent_name(
                    (sn.Name(module=t.maintype.module, name=t.maintype.name)
                     for t in targets),
                    module=source_name.module)

                target = so.ObjectRef(name=target_name)

                create_virt_parent = s_objtypes.CreateObjectType(
                    classname=target_name, metaclass=s_objtypes.ObjectType)

                create_virt_parent.update(
                    (sd.AlterObjectProperty(
                        property='bases',
                        new_value=so.ObjectList([
                            so.ObjectRef(
                                name=sn.Name(module='std', name='Object'))
                        ])),
                     sd.AlterObjectProperty(property='name',
                                            new_value=target_name),
                     sd.AlterObjectProperty(property='is_virtual',
                                            new_value=True)))

                delta_ctx = context.get(sd.DeltaRootContext)

                for cc in delta_ctx.op.get_subcommands(
                        type=s_objtypes.CreateObjectType):
                    if cc.classname == create_virt_parent.classname:
                        break
                else:
                    delta_ctx.op.add(create_virt_parent)
            else:
                target_expr = targets[0]
                if isinstance(target_expr, qlast.TypeName):
                    target = utils.ast_to_typeref(
                        target_expr,
                        modaliases=context.modaliases,
                        schema=schema)
                else:
                    # computable
                    target = cmd._parse_computable(target_expr, schema,
                                                   context)

            if (isinstance(target, so.ObjectRef)
                    and target.name == source_name):
                # Special case for loop links.  Since the target
                # is the same as the source, we know it's a proper
                # type.
                pass
            else:
                if target_type is None:
                    target_type = utils.resolve_typeref(target, schema=schema)

                if not isinstance(target_type, s_objtypes.ObjectType):
                    raise errors.InvalidLinkTargetError(
                        f'invalid link target, expected object type, got '
                        f'{target_type.__class__.__name__}',
                        context=astnode.target.context)

            cmd.add(sd.AlterObjectProperty(property='target',
                                           new_value=target))

            base_prop_name = sn.Name('std::source')
            s_name = sn.get_specialized_name(base_prop_name, cmd.classname)
            src_prop_name = sn.Name(name=s_name, module=cmd.classname.module)

            src_prop = lproperties.CreateProperty(
                classname=src_prop_name, metaclass=lproperties.Property)
            src_prop.update((
                sd.AlterObjectProperty(property='name',
                                       new_value=src_prop_name),
                sd.AlterObjectProperty(
                    property='bases',
                    new_value=[so.ObjectRef(name=base_prop_name)]),
                sd.AlterObjectProperty(
                    property='source',
                    new_value=so.ObjectRef(name=cmd.classname)),
                sd.AlterObjectProperty(
                    property='target',
                    new_value=so.ObjectRef(name=source_name)),
                sd.AlterObjectProperty(property='required', new_value=True),
                sd.AlterObjectProperty(property='readonly', new_value=True),
            ))

            cmd.add(src_prop)

            base_prop_name = sn.Name('std::target')
            s_name = sn.get_specialized_name(base_prop_name, cmd.classname)
            tgt_prop_name = sn.Name(name=s_name, module=cmd.classname.module)

            tgt_prop = lproperties.CreateProperty(
                classname=tgt_prop_name, metaclass=lproperties.Property)
            tgt_prop.update((
                sd.AlterObjectProperty(property='name',
                                       new_value=tgt_prop_name),
                sd.AlterObjectProperty(
                    property='bases',
                    new_value=[so.ObjectRef(name=base_prop_name)]),
                sd.AlterObjectProperty(
                    property='source',
                    new_value=so.ObjectRef(name=cmd.classname)),
                sd.AlterObjectProperty(property='target', new_value=target),
                sd.AlterObjectProperty(property='required', new_value=False),
                sd.AlterObjectProperty(property='readonly', new_value=True),
            ))

            cmd.add(tgt_prop)

            cls._parse_default(cmd)

        else:
            # this is an abstract link then
            if cmd.get_attribute_value('default') is not None:
                raise errors.SchemaDefinitionError(
                    f"'default' is not a valid field for an abstact link",
                    context=astnode.context)

        return cmd