visitTuple method

  1. @override
void visitTuple(
  1. Tuple e,
  2. void arg
)
override

Implementation

@override
void visitTuple(Tuple e, void arg) {
  if (!e.usedAsRowValue) return;

  bool isRowValue(Expression? expr) => expr is Tuple || expr is SubQuery;

  var parent = e.parent;
  var isAllowed = false;

  if (parent is WhenComponent && e == parent.when) {
    // look at the surrounding case expression
    parent = parent.parent;
  }

  if (parent is BinaryExpression) {
    // Source: https://www.sqlite.org/rowvalue.html#syntax
    const allowedTokens = [
      TokenType.less,
      TokenType.lessEqual,
      TokenType.more,
      TokenType.moreEqual,
      TokenType.equal,
      TokenType.doubleEqual,
      TokenType.lessMore,
      TokenType.$is,
    ];

    if (allowedTokens.contains(parent.operator.type)) {
      isAllowed = true;
    }
  } else if (parent is BetweenExpression) {
    // Allowed if all value are row values or subqueries
    isAllowed = !parent.childNodes.any((e) => !isRowValue(e));
  } else if (parent is CaseExpression) {
    // Allowed if we have something to compare against and all comparisons
    // are row values
    if (parent.base == null) {
      isAllowed = false;
    } else {
      final comparisons = <Expression?>[
        parent.base,
        for (final branch in parent.whens) branch.when
      ];

      isAllowed = !comparisons.any((e) => !isRowValue(e));
    }
  } else if (parent is InExpression) {
    // For in expressions we have a more accurate analysis on whether tuples
    // are allowed that looks at both the LHS and the RHS.
    isAllowed = true;
  } else if (parent is SetComponent) {
    isAllowed = true;
  }

  if (!isAllowed) {
    context.reportError(AnalysisError(
      type: AnalysisErrorType.rowValueMisuse,
      relevantNode: e,
      message: 'Row values can only be used as expressions in comparisons',
    ));
  }
}