Skip to content

Add table literal sugar for lists of records - #10796

Open
haydenflinner wants to merge 4 commits into
roc-lang:mainfrom
haydenflinner:table-literals
Open

Add table literal sugar for lists of records#10796
haydenflinner wants to merge 4 commits into
roc-lang:mainfrom
haydenflinner:table-literals

Conversation

@haydenflinner

@haydenflinner haydenflinner commented Aug 15, 2026

Copy link
Copy Markdown

Sorry about the original contents of this post, Cursor jumped a little ahead -- I ran out of Claude Code earlier today and have been trying the new old tool again :-) The below is human edited.

I had Claude add a table syntax to Roc's syntax, because I like tables, and I wondered if it was possible without knowing Roc to modify it.

Why tables matter

I think tables are critical to the future of computing. Some references:

or in text form:

image

The homepage todos example (couldn't find the source) is the kind of thing that currently repeats every field name on every row. A table literal makes that more convenient, IMO:

Suggested syntax (taken from Pyret)

Homepage writes:

    todos = [
        { name: "Learn Roc",       status: Done },
        { name: "Buy groceries",   status: Done },
        { name: "Write blog post", status: InProgress },
        { name: "Call mom",        status: NotStarted },
    ]

The suggested change in idiom is:

todos = table name, status {
    "Learn Roc",       Done,
    "Buy groceries",   Done,
    "Write blog post", InProgress,
    "Call mom",        NotStarted,
}

The most obvious difference compared to Pyret's choice is the lack of row. Pyret's row: would enable a single logical row span multiple source lines, I guess. Without it, my intuition is that each table row can only be one row in the source text.
Claude says that in the impl vibecoded, newlines at table-body depth end the row; newlines inside (), [], {}, or a lambda do not.
I don't have an opinion on that tradeoff, it's just what fell out.

table is a contextual keyword (table(x) stays valid, i.e. table is not reserved). Columns may take types (table name : Str, age : U8 { ... }) so numerals pick up the column type instead of defaulting to Dec.

Implementation

This PR is an allegedly working implementation of the suggested syntax that Claude wrote. I have not personally used it yet, I wanted to discuss the idea more before sinking in more time. I had claude write an impl mostly to see if it was possible.

It claims that this is tested:

parse + format + canonicalize desugar, with snapshots. Invalid tables (duplicate columns, width mismatch, zero columns) fail at parse so later stages never see a bad table.

@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds table-literal syntax and lowers it into existing lists of records, with parser, formatter, canonicalizer, LSP, documentation, and snapshot support.

  • Adds contextual recognition and row-oriented parsing for table literals.
  • Adds AST and node-store representations plus list-of-records canonicalization.
  • Adds formatting, selection-range traversal, diagnostics, documentation, and snapshots.

Confidence Score: 4/5

The PR is not yet safe to merge because a valid complete-width table row followed by a newline and comma is still parsed as malformed.

The newline branch finishes the row without consuming the comma, and table_row_next immediately starts the next cell at that same comma token, leaving the previously reported parser failure outstanding.

Files Needing Attention: src/parse/Parser.zig

Important Files Changed

Filename Overview
src/parse/Parser.zig Adds contextual table recognition and row parsing, but the previously reported complete-row newline-before-comma separator failure remains.
src/canonicalize/Can.zig Desugars validated table literals into lists of records and applies optional column annotations.
src/fmt/fmt.zig Formats table headers and rows while integrating table expressions into multiline layout decisions.
src/parse/AST.zig Adds table AST nodes, diagnostics, and S-expression serialization.
src/parse/NodeStore.zig Adds storage, scratch spans, and retrieval APIs for table columns and rows.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    Source[Table literal source] --> Parser[Parser row and column handling]
    Parser --> AST[Table AST]
    AST --> Canonicalizer[Canonicalization]
    Canonicalizer --> Records[List of records]
    AST --> Formatter[Formatter]
    AST --> LSP[LSP selection ranges]
Loading

Reviews (3): Last reviewed commit: "Treat table as a table literal only when..." | Re-trigger Greptile

Comment thread src/parse/Parser.zig
…comma.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src/parse/Parser.zig
…same line.

A value named table followed by another lowercase name on the next line was
parsed as a table, which broke host platforms that re-export table.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant