PRQL/prql · error · Error

Unexpected: assign to

Error message

Unexpected: assign to `{alias}` (move assign into the tuple: `[{alias} = ...]`)

What it means

This flags a common anti-pattern: assigning an alias to an expression that is already a tuple, like `tuple = (...)` where the expression itself is a tuple. The resolver suggests moving the assignment inside the tuple — `[{alias} = ...]` — because an alias on a tuple is meaningless or a mistake in the select/derive context.

Solutions

  1. Move the assignment inside the tuple: instead of `alias = {a, b}` write `{alias_a = a, alias_b = b}` or nest as intended.
  2. Drop the alias if the tuple's fields already have names.
  3. If you wanted one column from a tuple, extract a field instead of aliasing the whole tuple.

Example fix

// before
select name = {first, last}
// after
select {first, last}
Defensive patterns

Strategy: validation

Validate before calling

// when generating select/derive args, reject aliasing a whole tuple
if (arg.alias && arg.expr.kind === 'tuple') throw new Error('move assign inside the tuple: [' + arg.alias + ' = ...]');

Type guard

const aliasesTuple = (arg) => Boolean(arg.alias) && Array.isArray(arg.expr);

Try / catch

try { compile(prql) } catch (e) { if (e.message.includes('Unexpected: assign to')) { /* restructure the select/derive argument */ } }

Prevention

When it happens

Trigger: Writing `select name = {first, last}` or `derive x = (a, b)` where the right-hand side is already a tuple and the left-hand side adds an alias to it.

Common situations: Refactoring a single column into a tuple but keeping the old alias, misunderstanding PRQL's tuple/record syntax versus SQL `AS` aliasing, or copy-pasting from other query languages.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of PRQL/prql@e164e249b9 (2026-09-09). Data as JSON: /api/errors/bfbdfbb3c4aa75ba. Report an issue: GitHub.

Appendix: source

Thrown at prqlc/prqlc/src/semantic/resolver/transforms.rs:633

            partition: None,
            frame: WindowFrame::default(),
            sort: Vec::new(),
        };
        let ty = self.infer_type_of_special_func(&transform_call)?;
        Ok(Expr {
            ty,
            ..Expr::new(ExprKind::TransformCall(transform_call))
        })
    }

    /// Wraps non-tuple Exprs into a singleton Tuple.
    pub(super) fn coerce_into_tuple(&mut self, expr: Expr) -> Result<Expr> {
        let is_tuple_ty =
            expr.ty.as_ref().is_some_and(|t| t.kind.is_tuple()) && !expr.kind.is_all();
        Ok(if is_tuple_ty {
            // a helpful check for a common anti-pattern
            if let Some(alias) = expr.alias {
                return Err(Error::new(Reason::Unexpected {
                    found: format!("assign to `{alias}`"),
                })
                .push_hint(format!("move assign into the tuple: `[{alias} = ...]`"))
                .with_span(expr.span));
            }

            expr
        } else {
            let span = expr.span;
            let mut expr = Expr::new(ExprKind::Tuple(vec![expr]));
            expr.span = span;

            self.fold_expr(expr)?
        })
    }

    /// Figure out the type of a function call, if this function is a *special function*.
    /// (declared in std module & requires special handling).

View on GitHub (pinned to e164e249b9)