Ctrl K
ComunidadGuías…

src/analysis/move_check.c

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: MIT
#include "../constants.h"
#include "move_check.h"
#include "../parser/parser.h"
#include "typecheck.h"
#include "../diagnostics/diagnostics.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "zprep.h"

MoveState *move_state_create(MoveState *parent)
{
    MoveState *s = xmalloc(sizeof(MoveState));
    s->entries = NULL;
    s->parent = parent;
    return s;
}

MoveState *move_state_clone(MoveState *src)
{
    if (!src)
    {
        return NULL;
    }
    MoveState *new_state = move_state_create(src->parent);

    MoveEntry *curr = src->entries;
    MoveEntry **tail = &new_state->entries;
    while (curr)
    {
        MoveEntry *new_entry = xmalloc(sizeof(MoveEntry));
        new_entry->symbol_name = xstrdup(curr->symbol_name);
        new_entry->status = curr->status;
        new_entry->moved_at = curr->moved_at;
        new_entry->next = NULL;
        *tail = new_entry;
        tail = &new_entry->next;
        curr = curr->next;
    }
    return new_state;
}

void move_state_free(MoveState *state)
{
    if (!state)
    {
        return;
    }
    MoveEntry *e = state->entries;
    while (e)
    {
        MoveEntry *next = e->next;
        zfree(e->symbol_name);
        zfree(e);
        e = next;
    }
    zfree(state);
}

char *get_node_path(ASTNode *node, int depth, ParserContext *ctx)
{
    if (!node || depth > 32)
    {
        return NULL;
    }
    RECURSION_GUARD_TOKEN(ctx, node->token, NULL);

    char *path = NULL;

    if (node->kind == NODE_EXPR_VAR)
    {
        path = xstrdup(node->var_ref.name);
    }
    else if (node->kind == NODE_EXPR_MEMBER)
    {
        char *target_path = get_node_path(node->member.target, depth + 1, ctx);
        if (target_path)
        {
            char buffer[MAX_ERROR_MSG_LEN];
            snprintf(buffer, sizeof(buffer), "%s.%s", target_path, node->member.field);
            zfree(target_path);
            path = xstrdup(buffer);
        }
    }

    RECURSION_EXIT(ctx);
    return path;
}

static void mark_moved_in_state(MoveState *state, const char *path, Token t)
{
    if (!state || !path)
    {
        return;
    }

    MoveEntry *e = state->entries;
    while (e)
    {
        if (strcmp(e->symbol_name, path) == 0)
        {
            e->status = MOVE_STATE_MOVED;
            e->moved_at = t;
            return;
        }
        e = e->next;
    }

    MoveEntry *new_entry = xmalloc(sizeof(MoveEntry));
    new_entry->symbol_name = xstrdup(path);
    new_entry->status = MOVE_STATE_MOVED;
    new_entry->moved_at = t;
    new_entry->next = state->entries;
    state->entries = new_entry;
}

MoveStatus get_move_status(MoveState *state, const char *path)
{
    if (!path)
    {
        return MOVE_STATE_VALID;
    }
    MoveState *s = state;
    while (s)
    {
        MoveEntry *e = s->entries;
        while (e)
        {
            if (strcmp(e->symbol_name, path) == 0)
            {
                return e->status;
            }
            e = e->next;
        }
        s = s->parent;
    }
    return MOVE_STATE_VALID;
}

void move_state_merge(MoveState *target, MoveState *a, MoveState *b)
{
    if (!target)
    {
        return;
    }

    if (a)
    {
        MoveEntry *e = a->entries;
        while (e)
        {
            MoveStatus status_a = e->status;
            MoveStatus status_b = b ? get_move_status(b, e->symbol_name) : MOVE_STATE_VALID;

            if (status_a == MOVE_STATE_MOVED || status_b == MOVE_STATE_MOVED)
            {
                mark_moved_in_state(target, e->symbol_name, e->moved_at);
            }
            e = e->next;
        }
    }

    if (b)
    {
        MoveEntry *e = b->entries;
        while (e)
        {
            MoveStatus status_b = e->status;
            MoveStatus status_a = a ? get_move_status(a, e->symbol_name) : MOVE_STATE_VALID;

            if (status_b == MOVE_STATE_MOVED || status_a == MOVE_STATE_MOVED)
            {
                mark_moved_in_state(target, e->symbol_name, e->moved_at);
            }
            e = e->next;
        }
    }
}

void move_state_merge_into(MoveState **target, MoveState *src)
{
    if (!src)
    {
        return;
    }

    if (!*target)
    {
        *target = move_state_clone(src);
        return;
    }

    MoveEntry *e = src->entries;
    while (e)
    {
        if (e->status == MOVE_STATE_MOVED)
        {
            mark_moved_in_state(*target, e->symbol_name, e->moved_at);
        }
        e = e->next;
    }
}

int is_type_copy(ParserContext *ctx, Type *t)
{
    if (!t)
    {
        return 1; // Default to Copy for unknown types
    }
    RECURSION_GUARD_TOKEN(ctx, TOKEN_UNKNOWN, 1);

    int result = 1;
    if (t->traits.has_drop)
    {
        result = 0;
    }
    else if (t->name && check_impl(ctx, "Drop", t->name))
    {
        result = 0;
    }
    else
    {
        switch (t->kind)
        {
        case TYPE_INT:
        case TYPE_I8:
        case TYPE_I16:
        case TYPE_I32:
        case TYPE_I64:
        case TYPE_U8:
        case TYPE_U16:
        case TYPE_U32:
        case TYPE_U64:
        case TYPE_F32:
        case TYPE_F64:
        case TYPE_BOOL:
        case TYPE_CHAR:
        case TYPE_VOID:
        case TYPE_POINTER:
        case TYPE_FUNCTION:
            result = 1;
            break;
        case TYPE_ENUM:
        case TYPE_BITINT:
        case TYPE_UBITINT:
            result = 1;
            break;
        case TYPE_STRUCT:
            if (check_impl(ctx, "Copy", t->name))
            {
                result = 1;
            }
            else if (check_impl(ctx, "Drop", t->name))
            {
                result = 0;
            }
            else if (t->name)
            {
                ASTNode *def = find_struct_def(ctx, t->name);
                if (def && def->type_info && def->type_info->traits.has_drop)
                {
                    result = 0;
                }
                else
                {
                    result = 1;
                }
            }
            else
            {
                result = 1;
            }
            break;
        case TYPE_ARRAY:
            result = is_type_copy(ctx, t->inner);
            break;
        case TYPE_ALIAS:
            if (t->alias.is_opaque_alias)
            {
                result = 1;
            }
            else
            {
                result = is_type_copy(ctx, t->inner);
            }
            break;
        default:
            result = 1;
            break;
        }
    }
    RECURSION_EXIT(ctx);
    return result;
}

void check_path_validity(TypeChecker *tc, const char *path, Token t)
{
    if (!path)
    {
        return;
    }

    // Check Flow-Sensitive State
    ParserContext *ctx = tc->pctx;
    MoveStatus status = MOVE_STATE_VALID;

    if (ctx && ctx->move_state)
    {
        status = get_move_status(ctx->move_state, path);
    }

    if (status == MOVE_STATE_MOVED || status == MOVE_STATE_MAYBE_MOVED)
    {
        if (tc && tc->in_loop_pass2)
        {
            if (t.line == 0)
            {
                return;
            }
            if (tc->loop_start_state)
            {
                MoveStatus start_status = get_move_status(tc->loop_start_state, path);
                if (start_status == MOVE_STATE_MOVED || start_status == MOVE_STATE_MAYBE_MOVED)
                {
                    return;
                }
            }
        }

        char msg[MAX_ERROR_MSG_LEN];
        snprintf(msg, 255, "Use of moved value '%s'", path);

        const char *hints[] = {"This type owns resources and cannot be implicitly copied",
                               "Consider using a reference ('&') to borrow the value instead",
                               NULL};
        tc_move_error_with_hints(tc, t, msg, hints);
    }
}

void check_use_validity(TypeChecker *tc, ASTNode *use_node)
{
    if (!use_node)
    {
        return;
    }

    // The receiver of a .forget() call is expected to have been moved already
    // (pattern: `vec.push(x); x.forget();`), so do not report use-after-move.
    if (tc->is_forget_receiver)
    {
        return;
    }

    char *path = get_node_path(use_node, 0, tc->pctx);
    if (!path && use_node->kind == NODE_EXPR_VAR)
    {
        path = xstrdup(use_node->var_ref.name);
    }

    if (!path)
    {
        return;
    }

    check_path_validity(tc, path, use_node->token);
    zfree(path);
}

void mark_symbol_moved(ParserContext *ctx, ZenSymbol *sym, ASTNode *context_node)
{
    if (!context_node)
    {
        return;
    }

    Type *t = context_node->type_info ? context_node->type_info : (sym ? sym->type_info : NULL);
    int copy = is_type_copy(ctx, t);

    if (t && ctx && !copy)
    {
        if (sym)
        {
            sym->is_moved = 1;
        }

        if (ctx->move_state)
        {
            char *path = get_node_path(context_node, 0, ctx);
            if (!path && sym)
            {
                path = xstrdup(sym->name);
            }

            if (path)
            {
                mark_moved_in_state(ctx->move_state, path, context_node->token);
                zfree(path);
            }
        }
    }
}

static void mark_valid_in_state(MoveState *state, const char *path, Token t)
{
    if (!state || !path)
    {
        return;
    }

    MoveEntry *e = state->entries;
    while (e)
    {
        if (strcmp(e->symbol_name, path) == 0)
        {
            e->status = MOVE_STATE_VALID;
            e->moved_at = t;
            return;
        }
        e = e->next;
    }

    MoveEntry *new_entry = xmalloc(sizeof(MoveEntry));
    new_entry->symbol_name = xstrdup(path);
    new_entry->status = MOVE_STATE_VALID;
    new_entry->moved_at = t;
    new_entry->next = state->entries;
    state->entries = new_entry;
}

void mark_symbol_valid(ParserContext *ctx, ZenSymbol *sym, ASTNode *context_node)
{
    if (sym)
    {
        sym->is_moved = 0;
    }

    if (ctx && ctx->move_state)
    {
        char *path = get_node_path(context_node, 0, ctx);
        if (!path && sym)
        {
            path = xstrdup(sym->name);
        }

        if (path)
        {
            mark_valid_in_state(ctx->move_state, path,
                                context_node ? context_node->token
                                             : (sym ? sym->decl_token : TOKEN_UNKNOWN));
            zfree(path);
        }
    }
}