Skip to content

Code generator produces non-optional types for link[is SubType]: { shape } syntax causing panic #398

@craigdrayton

Description

@craigdrayton

gel-go Bug Report: Incorrect Code Generation for Polymorphic Type Intersections

Summary

gel-go v1.4.3 generates incorrect Go types for polymorphic type intersection queries using the syntax field[is SubType]: { ... }, causing a runtime panic when the actual data doesn't match the type filter.

Panic: interface conversion: *codecs.objectDecoder is not codecs.OptionalDecoder: missing method DecodeMissing

Environment

  • gel-go version: v1.4.3
  • EdgeDB version: 6.11+ / 7.0+
  • Go version: 1.23+ (likely all versions)
  • Platform: Windows (likely all platforms)

The Problem

Valid EdgeQL That Triggers Bug

SELECT Item {
    fields: {
        name,
        field_type[is ConceptLinkFieldType]: {
            target: { id }
        }
    }
}

Generated Go code (INCORRECT):

type FieldsItem struct {
    Name      string        `gel:"name"`
    FieldType FieldTypeItem `gel:"field_type"`  // ← Not optional!
}

type FieldTypeItem struct {
    Target TargetItem `gel:"target"`  // ← Not optional!
}

Runtime behavior: When a field's field_type is PrimitiveFieldType (not ConceptLinkFieldType), the type intersection returns empty/null, but gel-go tries to decode it into a non-optional struct → panic.

Working Syntax (Generates Correct Code)

SELECT Item {
    fields: {
        name,
        field_type: {
            [is ConceptLinkFieldType].target: { id }
        }
    }
}

Generated Go code (CORRECT):

type FieldsItem struct {
    Name      string        `gel:"name"`
    FieldType FieldTypeItem `gel:"field_type"`
}

type FieldTypeItem struct {
    Target TargetItem `gel:"target"`
}

type TargetItem struct {
    geltypes.Optional  // ← Correctly optional!
    Id geltypes.UUID   `gel:"id"`
}

Minimal Reproduction

1. Schema

module default {
    abstract type FieldType {}

    type PrimitiveFieldType extending FieldType {
        required property identifier -> str;
    }

    type ConceptLinkFieldType extending FieldType {
        required link target -> Item;
    }

    type Item {
        required property name -> str;
        multi link fields -> Field;
    }

    type Field {
        required property name -> str;
        required link item -> Item;
        required link field_type -> FieldType;
    }
}

2. Create Test Data

-- Create item
WITH item := (INSERT Item { name := 'Test' })
-- Create field with PRIMITIVE type (not ConceptLink)
INSERT Field {
    name := 'TestField',
    item := item,
    field_type := (INSERT PrimitiveFieldType { identifier := 'text' })
};

3. Query with Bug-Triggering Syntax

-- This will panic when executed via gel-go
SELECT Item {
    name,
    fields: {
        name,
        field_type[is ConceptLinkFieldType]: {
            target: { id }
        }
    }
}

Expected: Should return with field_type empty/null since the actual type is PrimitiveFieldType

Actual: Panic at runtime: interface conversion: *codecs.objectDecoder is not codecs.OptionalDecoder

4. Verify Working Syntax

-- This works correctly
SELECT Item {
    name,
    fields: {
        name,
        field_type: {
            [is ConceptLinkFieldType].target: { id }
        }
    }
}

Returns successfully with target: null for primitive fields.

Root Cause

The syntax link[is SubType]: { shape } is valid EdgeQL documented in the official EdgeDB SELECT documentation (e.g., characters[is Hero]: { secret_identity }).

However, gel-go's code generator does not recognize that this creates an optional shape (since the type filter may not match). It generates non-optional struct fields, causing a panic when decoding null/empty data from non-matching types.

The workaround syntax field: { [is SubType].property } correctly generates optional types.

Expected Behavior

Both syntaxes should generate equivalent Optional types:

type FieldTypeItem struct {
    geltypes.Optional  // ← Should be optional for both syntaxes
    Target TargetItem `gel:"target"`
}

Workaround

Use the alternative syntax: field: { [is SubType].property } instead of field[is SubType]: { property }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions