Skip to content

Fix compound literals to capture all field values, not just first #299

@jserv

Description

@jserv

Description

Struct compound literals currently only capture the first field value, ignoring subsequent fields. This severely limits their usefulness and violates C99 semantics. Array compound literals also have non-standard behavior that needs correction.

Current Bug

// Only first field is captured
struct point { int x, y; };
struct point p = (struct point){10, 20};  // y is lost, becomes 0

// Array compound literals broken
int *arr = (int[]){1, 2, 3, 4, 5};  // Non-standard behavior

C99 Standard Reference

C99 §6.5.2.5 Compound literals:

A compound literal provides an unnamed object whose value is given by the initializer list.

The entire initializer list must be processed, not just the first element.

Root Cause Analysis

In src/parser.c, the compound literal parsing only processes one field:

// Current broken implementation
if (is_struct) {
    read_parameter_list_decl();  // Only reads one value!
    // Missing: loop to read all fields
}

Proposed Fix

1. Complete Struct Compound Literal Parsing

// src/parser.c - Fix compound literal parsing
void read_compound_literal(type_t *type) {
    lex_expect('{');
    
    if (type->base_type == TYPE_struct) {
        parse_struct_compound_literal(type);
    } else if (type->base_type == TYPE_array) {
        parse_array_compound_literal(type);
    } else {
        error("compound literal requires struct or array type");
    }
    
    lex_expect('}');
}

void parse_struct_compound_literal(type_t *struct_type) {
    // Allocate temporary storage for the struct
    var_t *temp = add_tmp_var(current_block, "compound_lit", struct_type);
    
    int field_index = 0;
    struct_field_t *field = struct_type->fields;
    
    while (!lex_peek_token('}')) {
        if (field_index > 0) {
            if (!lex_accept(',')) {
                break;  // No more initializers
            }
        }
        
        // Check for designated initializer
        if (lex_peek_token('.')) {
            field = parse_designated_field_init(struct_type);
        }
        
        if (!field) {
            error("too many initializers for struct");
            break;
        }
        
        // Parse initializer expression
        read_expr();
        var_t *value = pop_var();
        
        // Generate store to field
        var_t *field_ptr = get_struct_field_ptr(temp, field);
        emit_store(field_ptr, value, field->type);
        
        // Move to next field (if not designated)
        if (!designated) {
            field = field->next;
            field_index++;
        }
    }
    
    // Initialize remaining fields to zero
    while (field) {
        var_t *field_ptr = get_struct_field_ptr(temp, field);
        emit_store(field_ptr, zero_value(field->type), field->type);
        field = field->next;
    }
    
    push_var(temp);
}

2. Fix Array Compound Literals

void parse_array_compound_literal(type_t *array_type) {
    // Count elements first (if not specified)
    int element_count = 0;
    if (array_type->array_size == 0) {
        element_count = count_initializer_elements();
        array_type->array_size = element_count;
    } else {
        element_count = array_type->array_size;
    }
    
    // Allocate array storage
    var_t *temp = add_tmp_var(current_block, "array_lit", array_type);
    
    int index = 0;
    while (!lex_peek_token('}') && index < element_count) {
        if (index > 0) {
            if (!lex_accept(',')) {
                break;
            }
        }
        
        // Check for designated initializer
        if (lex_peek_token('[')) {
            index = parse_designated_index_init();
        }
        
        // Parse element
        read_expr();
        var_t *value = pop_var();
        
        // Store to array element
        var_t *elem_ptr = get_array_element_ptr(temp, index);
        emit_store(elem_ptr, value, array_type->element_type);
        
        index++;
    }
    
    // Zero remaining elements
    while (index < element_count) {
        var_t *elem_ptr = get_array_element_ptr(temp, index);
        emit_store(elem_ptr, zero_value(array_type->element_type), 
                  array_type->element_type);
        index++;
    }
    
    push_var(temp);
}

3. Support Designated Initializers

struct_field_t *parse_designated_field_init(type_t *struct_type) {
    lex_expect('.');
    char *field_name = read_identifier();
    lex_expect('=');
    
    // Find field by name
    struct_field_t *field = struct_type->fields;
    while (field) {
        if (strcmp(field->name, field_name) == 0) {
            return field;
        }
        field = field->next;
    }
    
    error("no field '%s' in struct", field_name);
    return NULL;
}

int parse_designated_index_init() {
    lex_expect('[');
    int index = read_constant_expression();
    lex_expect(']');
    lex_expect('=');
    return index;
}

4. Storage Duration and Lifetime

// Compound literals have automatic storage duration (stack)
// unless they appear at file scope (static storage)

var_t *create_compound_literal_storage(type_t *type) {
    if (current_scope == SCOPE_GLOBAL) {
        // Static storage duration
        return add_global_var(gen_label("compound"), type);
    } else {
        // Automatic storage duration
        return add_local_var(current_func, gen_label("compound"), type);
    }
}

Test Cases

// tests/compound_literal_test.c

void test_struct_compound_literals() {
    struct point { int x, y; };
    
    // Basic compound literal
    struct point p1 = (struct point){10, 20};
    assert(p1.x == 10);
    assert(p1.y == 20);  // Currently fails!
    
    // Partial initialization
    struct point p2 = (struct point){.x = 5};
    assert(p2.x == 5);
    assert(p2.y == 0);  // Uninitialized fields -> 0
    
    // Designated initializers
    struct point p3 = (struct point){.y = 30, .x = 25};
    assert(p3.x == 25);
    assert(p3.y == 30);
    
    // Compound literal in expression
    int sum = ((struct point){100, 200}).x + 
              ((struct point){100, 200}).y;
    assert(sum == 300);
}

void test_array_compound_literals() {
    // Basic array compound literal
    int *arr1 = (int[]){1, 2, 3, 4, 5};
    assert(arr1[0] == 1);
    assert(arr1[4] == 5);
    
    // With size specified
    int *arr2 = (int[10]){1, 2, 3};  // Rest are 0
    assert(arr2[0] == 1);
    assert(arr2[3] == 0);
    assert(arr2[9] == 0);
    
    // Designated initializers
    int *arr3 = (int[]){[0] = 10, [5] = 50, [2] = 20};
    assert(arr3[0] == 10);
    assert(arr3[2] == 20);
    assert(arr3[5] == 50);
}

void test_nested_compound_literals() {
    struct nested {
        struct point { int x, y; } p;
        int z;
    };
    
    struct nested n = (struct nested){
        .p = (struct point){10, 20},
        .z = 30
    };
    
    assert(n.p.x == 10);
    assert(n.p.y == 20);
    assert(n.z == 30);
}

void test_compound_literal_lifetime() {
    int *get_array() {
        return (int[]){1, 2, 3};  // Warning: returns local
    }
    
    // Compound literal has automatic storage duration
    // Returning it is undefined behavior (like returning local array)
}

Implementation Priority

  1. Fix struct compound literals to capture all fields
  2. Fix array compound literals
  3. Add designated initializer support
  4. Ensure proper storage duration semantics

Success Criteria

  • All struct fields captured in compound literals
  • Array compound literals work correctly
  • Designated initializers supported
  • Proper storage duration (auto/static)
  • No regression in existing code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions