Skip to content

Commit

Permalink
feat: named unions (PRQL#4038)
Browse files Browse the repository at this point in the history
  • Loading branch information
aljazerzen authored and max-sixty committed Jan 11, 2024
1 parent 198bb52 commit 2bb3e65
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 27 deletions.
2 changes: 1 addition & 1 deletion prqlc/Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ tasks:
cmds:
- cmd: |
# remove trailing whitespace
rg '\s+$' --files-with-matches --glob '!*.rs' . \
rg '\s+$' --files-with-matches --glob '!*.{rs,snap}' . \
| xargs -I _ sh -c "echo Removing trailing whitespace from _ && sd '[\t ]+$' '' _"
- cmd: |
Expand Down
31 changes: 28 additions & 3 deletions prqlc/prql-compiler/src/codegen/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,24 @@ impl WriteSource for TyKind {
Ident(ident) => ident.write(opt),
Primitive(prim) => Some(prim.to_string()),
Union(variants) => {
let variants: Vec<_> = variants.iter().map(|x| &x.1).collect();
let parenthesize =
// never must be parenthesized
variants.is_empty() ||
// named union must be parenthesized
variants.iter().any(|(n, _)| n.is_some());

SeparatedExprs {
let variants: Vec<_> = variants.iter().map(|(n, t)| UnionVariant(n, t)).collect();
let sep_exprs = SeparatedExprs {
exprs: &variants,
inline: " || ",
line_end: " ||",
};

if parenthesize {
sep_exprs.write_between("(", ")", opt)
} else {
sep_exprs.write(opt)
}
.write(opt)
}
Singleton(lit) => Some(lit.to_string()),
Tuple(elements) => SeparatedExprs {
Expand Down Expand Up @@ -101,3 +111,18 @@ impl WriteSource for TupleField {
}
}
}

struct UnionVariant<'a>(&'a Option<String>, &'a Ty);

impl WriteSource for UnionVariant<'_> {
fn write(&self, mut opt: WriteOpt) -> Option<String> {
let mut r = String::new();
if let Some(name) = &self.0 {
r += name;
r += " = ";
}
opt.consume_width(r.len() as u16);
r += &self.1.write(opt)?;
Some(r)
}
}
18 changes: 9 additions & 9 deletions prqlc/prql-compiler/src/semantic/resolver/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,13 @@ pub(crate) fn normalize_type(ty: Ty) -> Ty {
for (variant_name, variant_ty) in variants {
let variant_ty = normalize_type(variant_ty);

// A | () = A
// (A || ()) = A
// skip never
if variant_ty.is_never() {
if variant_ty.is_never() && variant_name.is_none() {
continue;
}

// A | A | B = A | B
// (A || A || B) = A || B
// skip duplicates
let already_included = res.iter().any(|(_, r)| is_super_type_of(r, &variant_ty));
if already_included {
Expand All @@ -272,7 +272,7 @@ pub(crate) fn normalize_type(ty: Ty) -> Ty {

TyKind::Difference { base, exclude } => {
let (base, exclude) = match (*base, *exclude) {
// (A | B) - C = (A - C) | (B - C)
// (A || B) - C = (A - C) || (B - C)
(
Ty {
kind: TyKind::Union(variants),
Expand All @@ -297,7 +297,7 @@ pub(crate) fn normalize_type(ty: Ty) -> Ty {
);
return normalize_type(Ty { kind, name, span });
}
// (A - B) - C = A - (B | C)
// (A - B) - C = A - (B || C)
(
Ty {
kind:
Expand All @@ -318,9 +318,9 @@ pub(crate) fn normalize_type(ty: Ty) -> Ty {

// A - (B - C) =
// = A & not (B & not C)
// = A & (not B | C)
// = (A & not B) | (A & C)
// = (A - B) | (A & C)
// = A & (not B || C)
// = (A & not B) || (A & C)
// = (A - B) || (A & C)
(
a,
Ty {
Expand Down Expand Up @@ -450,7 +450,7 @@ pub(crate) fn normalize_type(ty: Ty) -> Ty {
let base = Box::new(normalize_type(base));
let exclude = Box::new(normalize_type(exclude));

// A - (A | B) = ()
// A - (A || B) = ()
if let TyKind::Union(excluded) = &exclude.kind {
for (_, e) in excluded {
if base.as_ref() == e {
Expand Down
4 changes: 2 additions & 2 deletions prqlc/prql-compiler/src/sql/gen_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -813,8 +813,8 @@ pub(super) fn translate_operand(
/// parentheses are not required. Some examples of when parentheses are not required:
/// - `(a - b) - c` & `(a + b) - c` — as opposed to `a - (b - c)`
/// - `a + (b - c)` & `a + (b + c)` — as opposed to `a - (b + c)` & `a - (b - c)`
///
///
///
///
//
// If it were possible to evaluate this with less context that would be
// preferable, but it's not clear how to do that. (For example, even if we
Expand Down
2 changes: 1 addition & 1 deletion prqlc/prql-compiler/tests/integration/error_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ fn date_to_text_with_column_format() {
fn date_to_text_unsupported_chrono_item() {
assert_display_snapshot!(compile(r#"
prql target:sql.duckdb
from [{d = @2021-01-01}]
derive {
d_str = d | date.to_text "%_j"
Expand Down
25 changes: 22 additions & 3 deletions prqlc/prql-compiler/tests/integration/resolving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ fn resolve_types_01() {
}

#[test]
#[ignore]
fn resolve_types_02() {
assert_snapshot!(resolve(r#"
type A = A || ()
type A = int || ()
"#).unwrap(), @r###"
type A = A
type A = int
"###)
}

Expand All @@ -63,3 +62,23 @@ fn resolve_types_03() {
type A = {a = int, bool, b = text, float}
"###)
}

#[test]
fn resolve_types_04() {
assert_snapshot!(resolve(
r#"
type Status = (
Paid = () ||
Unpaid = float ||
Canceled = {reason = text, cancelled_at = timestamp} ||
)
"#,
)
.unwrap(), @r###"
type Status = (
Paid = () ||
Unpaid = float ||
{reason = text, cancelled_at = timestamp} ||
)
"###);
}
2 changes: 1 addition & 1 deletion prqlc/prql-compiler/tests/integration/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ fn test_precedence() {
assert_display_snapshot!((compile(r###"
from numbers
derive {
sum_1 = a + b,
sum_1 = a + b,
sum_2 = add a b,
g = -a
}
Expand Down
8 changes: 4 additions & 4 deletions prqlc/prqlc-parser/src/interpolation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn parse_interpolate() {
let span_base = ParserSpan::new(0, 0..0);

assert_debug_snapshot!(
parse("concat({a})".to_string(), span_base).unwrap(),
parse("concat({a})".to_string(), span_base).unwrap(),
@r###"
[
String(
Expand Down Expand Up @@ -55,7 +55,7 @@ fn parse_interpolate() {
"###);

assert_debug_snapshot!(
parse("print('{{hello}}')".to_string(), span_base).unwrap(),
parse("print('{{hello}}')".to_string(), span_base).unwrap(),
@r###"
[
String(
Expand All @@ -65,7 +65,7 @@ fn parse_interpolate() {
"###);

assert_debug_snapshot!(
parse("concat('{{', a, '}}')".to_string(), span_base).unwrap(),
parse("concat('{{', a, '}}')".to_string(), span_base).unwrap(),
@r###"
[
String(
Expand All @@ -75,7 +75,7 @@ fn parse_interpolate() {
"###);

assert_debug_snapshot!(
parse("concat('{{', {a}, '}}')".to_string(), span_base).unwrap(),
parse("concat('{{', {a}, '}}')".to_string(), span_base).unwrap(),
@r###"
[
String(
Expand Down
24 changes: 23 additions & 1 deletion prqlc/prqlc-parser/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,28 @@ pub fn type_expr() -> impl Parser<Token, Ty, Error = PError> {
.map(TyKind::Tuple)
.labelled("tuple");

let union_parenthesized = ident_part()
.then_ignore(ctrl('='))
.or_not()
.then(nested_type_expr.clone())
.padded_by(new_line().repeated())
.separated_by(just(Token::Or))
.allow_trailing()
.then_ignore(new_line().repeated())
.delimited_by(ctrl('('), ctrl(')'))
.recover_with(nested_delimiters(
Token::Control('('),
Token::Control(')'),
[
(Token::Control('{'), Token::Control('}')),
(Token::Control('('), Token::Control(')')),
(Token::Control('['), Token::Control(']')),
],
|_| vec![],
))
.map(TyKind::Union)
.labelled("union");

let array = nested_type_expr
.map(Box::new)
.padded_by(new_line().repeated())
Expand All @@ -98,7 +120,7 @@ pub fn type_expr() -> impl Parser<Token, Ty, Error = PError> {
.map(TyKind::Array)
.labelled("array");

let term = choice((basic, ident, func, tuple, array))
let term = choice((basic, ident, func, tuple, array, union_parenthesized))
.map_with_span(into_ty)
.boxed();

Expand Down
4 changes: 2 additions & 2 deletions prqlc/prqlc/tests/snapshots/test__debug.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ info:
- debug
- resolve
env:
RUST_BACKTRACE: ""
CLICOLOR_FORCE: ""
NO_COLOR: "1"
RUST_BACKTRACE: ""
RUST_LOG: ""
NO_COLOR: "1"
stdin: from tracks
---
success: true
Expand Down

0 comments on commit 2bb3e65

Please sign in to comment.