Skip to content

Add generic struct/enum definitions to LUMOS #54

@rz1989s

Description

@rz1989s

Goal

Support generic type parameters on structs and enums for reusable, type-safe containers.

Phase: 5.3 Advanced Type System


Problem

Current limitations:

  • ❌ Can't create generic containers like Box<T>, Wrapper<T>
  • ❌ Code duplication for similar types
  • ❌ Can't match Rust's generic patterns
  • ❌ Limited reusability

Solution

Add generic type parameters to struct and enum definitions.

Syntax

// Generic struct
struct Wrapper<T> {
    value: T,
}

// Generic enum
enum Result<T, E> {
    Ok(T),
    Err(E),
}

// Multiple type parameters
struct Pair<A, B> {
    first: A,
    second: B,
}

Usage

struct PlayerData {
    name: String,
    level: u16,
}

struct GameState {
    player: Wrapper<PlayerData>,      // Wrapper<PlayerData>
    rewards: Vec<Wrapper<PublicKey>>, // Vec<Wrapper<PublicKey>>
}

Generated Code

Rust

#[derive(BorshSerialize, BorshDeserialize)]
pub struct Wrapper<T: BorshSerialize + BorshDeserialize> {
    pub value: T,
}

#[derive(BorshSerialize, BorshDeserialize)]
pub enum Result<T: BorshSerialize + BorshDeserialize, 
                 E: BorshSerialize + BorshDeserialize> {
    Ok(T),
    Err(E),
}

pub struct GameState {
    pub player: Wrapper<PlayerData>,
    pub rewards: Vec<Wrapper<Pubkey>>,
}

TypeScript

export interface Wrapper<T> {
  value: T;
}

export type Result<T, E> = 
  | { kind: 'Ok'; value: T }
  | { kind: 'Err'; value: E };

export interface GameState {
  player: Wrapper<PlayerData>;
  rewards: Wrapper<PublicKey>[];
}

// Borsh schema (monomorphized)
export const WrapperPlayerDataBorshSchema = borsh.struct([
  ['value', PlayerDataBorshSchema],
]);

export const WrapperPublicKeyBorshSchema = borsh.struct([
  ['value', borsh.publicKey()],
]);

Implementation

1. Parser Changes

Location: packages/core/src/parser.rs

pub struct TypeParameter {
    pub name: Ident,
    pub bounds: Vec<TraitBound>,  // Future: trait bounds
}

pub struct StructDef {
    pub name: Ident,
    pub type_params: Vec<TypeParameter>,  // NEW
    pub fields: Vec<Field>,
}

impl StructDef {
    pub fn parse_with_generics(input: &str) -> Result<Self, Error> {
        // Parse: struct Name<T, U> { ... }
    }
}

2. Type Instantiation

Location: packages/core/src/transform.rs

struct TypeInstantiator {
    type_params: HashMap<String, Type>,
}

impl TypeInstantiator {
    fn instantiate(&self, ty: &Type) -> Type {
        match ty {
            Type::TypeParam(name) => {
                self.type_params.get(name)
                    .cloned()
                    .unwrap_or(ty.clone())
            }
            Type::Generic { base, args } => {
                // Instantiate generic type with concrete args
                Type::Instantiated {
                    base: base.clone(),
                    args: args.iter().map(|a| self.instantiate(a)).collect(),
                }
            }
            _ => ty.clone(),
        }
    }
}

3. Monomorphization

For Borsh schemas, monomorphize generic types:

fn monomorphize_generic(
    generic: &StructDef, 
    args: &[Type]
) -> StructDef {
    let mut instantiated = generic.clone();
    
    // Replace type parameters with concrete types
    for (param, arg) in generic.type_params.iter().zip(args) {
        instantiated = replace_type_param(instantiated, param, arg);
    }
    
    instantiated
}

4. Rust Generator

Location: packages/core/src/generators/rust.rs

fn generate_struct_signature(struct_def: &StructDef) -> String {
    let name = &struct_def.name;
    
    if struct_def.type_params.is_empty() {
        format\!("pub struct {}", name)
    } else {
        let params = struct_def.type_params.iter()
            .map(|p| format\!("{}: BorshSerialize + BorshDeserialize", p.name))
            .collect::<Vec<_>>()
            .join(", ");
        format\!("pub struct {}<{}>", name, params)
    }
}

5. TypeScript Generator

Location: packages/core/src/generators/typescript.rs

fn generate_generic_interface(struct_def: &StructDef) -> String {
    let name = &struct_def.name;
    
    if struct_def.type_params.is_empty() {
        format\!("export interface {}", name)
    } else {
        let params = struct_def.type_params.iter()
            .map(|p| p.name.as_str())
            .collect::<Vec<_>>()
            .join(", ");
        format\!("export interface {}<{}>", name, params)
    }
}

// Generate monomorphized Borsh schemas
fn generate_monomorphized_schemas(usage_sites: &[TypeUsage]) -> String {
    // For each Wrapper<T> usage, generate WrapperTBorshSchema
}

Examples

Example 1: Optional Wrapper

struct Optional<T> {
    has_value: bool,
    value: T,
}

struct Player {
    nickname: Optional<String>,
}

Example 2: Pair Container

struct Pair<A, B> {
    first: A,
    second: B,
}

struct Trade {
    offer: Pair<PublicKey, u64>,  // (NFT, Price)
}

Example 3: Generic Enum

enum Status<T> {
    Pending,
    Active(T),
    Completed(T),
}

struct Game {
    state: Status<PublicKey>,  // Player who's active
}

Constraints & Limitations

Borsh Constraint

All type parameters must implement Borsh:

struct Wrapper<T: BorshSerialize + BorshDeserialize> { ... }

Monomorphization Required

TypeScript Borsh schemas are monomorphized (one per instantiation):

// Generic definition
interface Wrapper<T> { value: T }

// Monomorphized Borsh schemas
const WrapperPublicKeyBorshSchema = ...
const WrapperStringBorshSchema = ...

No Higher-Kinded Types

Only simple generics (no F<_>):

struct Container<T> { ... }        // ✓ Supported
struct Functor<F<_>> { ... }       // ✗ Not supported

Testing

Unit Tests

#[test]
fn test_parse_generic_struct() {
    let schema = parse_schema("struct Wrapper<T> { value: T }");
    assert_eq\!(schema.types[0].type_params.len(), 1);
}

#[test]
fn test_instantiate_generic() {
    let wrapper_def = /* Wrapper<T> */;
    let instantiated = instantiate(&wrapper_def, &[Type::PublicKey]);
    // Verify T replaced with PublicKey
}

#[test]
fn test_generate_generic_rust() {
    let generic = /* ... */;
    let rust = generate_struct(&generic);
    assert\!(rust.contains("struct Wrapper<T: BorshSerialize>"));
}

Integration Tests

  • Parse generic structs and enums
  • Instantiate with concrete types
  • Generate Rust generics
  • Generate TypeScript generics
  • Monomorphize Borsh schemas
  • Compile generated Rust code

Success Criteria

  • Parser accepts generic type parameters <T>
  • Supports multiple type params <A, B, C>
  • Type parameter substitution works
  • Generates correct Rust generics with bounds
  • Generates correct TypeScript generics
  • Monomorphizes Borsh schemas correctly
  • Generic enums supported
  • Nested generics work (Vec<Wrapper<T>>)
  • All tests passing
  • Documentation with examples

Documentation

Create New Files

  • docs/advanced/generics.md - Generics guide
  • docs/advanced/monomorphization.md - Borsh schema generation

Update Files

  • docs/syntax.md - Document generic syntax
  • README.md - Add generic example
  • CHANGELOG.md - Document new feature
  • ROADMAP.md - Mark Phase 5.3 item as complete ✅

Future Enhancements

  • Trait bounds: T: Clone + Debug
  • Where clauses: where T: Sized
  • Associated types
  • Const generics: struct Array<T, const N: usize>

Related


Priority: Medium
Complexity: Very High
Timeline: 7-10 days


📌 Remember: Update ROADMAP.md after completing this issue!

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreCore compiler (parser, generator, IR, transform)type:enhancementImprovement to existing featuretype:featureNew feature or functionality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions