Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add syntax for match and variant operators #1206

Merged
merged 15 commits into from
Oct 5, 2023
Merged

Conversation

shonfeder
Copy link
Contributor

@shonfeder shonfeder commented Oct 5, 2023

Closes #1082

This is an alternative to #1093, based on @konnov's preference to use the
existing convention of encoding language constructs as builtin operators with
special interpretations instead of extending our IR.

It adds support for:

  • Generating variant value constructors from type definitions
  • Parsing match expressions

It also includes some fixes and clean up from the last PR on sum types.

A notable alteration:

I am replacing the existing match syntax used for the unsupported record unions with the superseding sum-type syntax agreed upon in #1062. This means a "regression" in our options.qnt example, since this used the partially supported union syntax. However, afaiu, this really just reflects more accurately the state of our support for this kind of construct at the moment.

  • Tests added for any new code
  • Documentation added for any new functionality
  • Entries added to the respective CHANGELOG.md for any new functionality
  • Feature table on README.md updated for any listed functionality

@shonfeder shonfeder marked this pull request as draft October 5, 2023 04:02
Shon Feder added 12 commits October 5, 2023 00:03
Move helper function into its own private method
This is covered in the example test
This marks our switch to sum types from the union of records.
Record unions are not really supported, and we mean to ditch them, so
this is a more honest report of the current state of things.
We use it in ToIrListener now, not just in the tests.
Expected goes second. This effects the output.
return group([text('{ '), prettySumRow(type.fields), text('}')])
return prettySumRow(type.fields)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cleanup from the previous PR adding the sum types. The were wrapped in an extra {...}

@shonfeder shonfeder marked this pull request as ready for review October 5, 2023 05:59
@shonfeder shonfeder requested review from bugarela and thpani October 5, 2023 05:59
Copy link
Collaborator

@bugarela bugarela left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax looks super neat!

fieldType = poppedType
}

// const poppedType = this.popType().value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// const poppedType = this.popType().value
//

const labelStr: QuintStr = { id: caseId, kind: 'str', value: label }
const elim: QuintLambda = { id: caseId, kind: 'lambda', qualifier: 'def', expr: caseExpr, params }
return [labelStr, elim]
// return acc.concat([labelStr, elim])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// return acc.concat([labelStr, elim])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching these!

@@ -29,3 +30,17 @@ import JSONbig from 'json-bigint'
export function unreachable(object: never): never {
throw new Error(`impossible: non-exhuastive check should fail during type checking ${JSONbig.stringify(object)}`)
}

/** A wrapper around lodash zip that ensures all zipped elements are defined
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use this! Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I noticed this was moved, but regardless, I didn't know/remember we have this 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was added in 99910e3 :)

matchSumExpr: MATCH expr '{' '|'? matchCase+=matchSumCase ('|' matchCase+=matchSumCase)* '}' ;
matchSumCase: (variantMatch=matchSumVariant | wildCardMatch='_') '=>' expr ;
matchSumVariant
: (variantLabel=simpleId["variant label"]) ('(' (variantParam=simpleId["match case parameter"] | '_') ')')? ;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are these labels used? I see they have spaces, so I suppose it's only for error reporting to users? If so, that's really great - we should really use them more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, the string arguments are used for error reporting in

// An unqualified identifier that raises an error if a qualId is supplied
simpleId[context: string]
: IDENTIFIER
| qualId {
const err = quintErrorToString(
{ code: 'QNT008',
message: "Identifiers in a " + $context + " cannot be qualified with '::'. Found " + $qualId.text + "."
},
)
this.notifyErrorListeners(err)
}
;

To produce messages like

>>> {A::B::c: 3}
syntax error: error: [QNT008] Identifiers in a record cannot be qualified with '::'. Found A::B::c.
{A::B::c: 3}
        ^


>>> type T = A::B(int)
syntax error: error: [QNT008] Identifiers in a variant label cannot be qualified with '::'. Found A::B.
type T = A::B(int)
             ^

The ANTLR labels like variantLabel and variantParam, on the other hand, are used to provide named accessors for the parsed data on the rhs of the =, used in

if (variant._variantParam) {
name = variant._variantParam.text
} else {
// We either have a hole or no param specified, in which case our lambda only needs a hole
name = '_'
}
label = variant._variantLabel.text
params = [{ name, id: this.getId(variant) }]

@shonfeder
Copy link
Contributor Author

Thank you for the careful review!

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.

Add syntax for sum-types
2 participants