private static Map<Symbol, Symbol> computeIdentityTranslations(
     Map<Symbol, Expression> assignments) {
   Map<Symbol, Symbol> inputToOutput = new HashMap<>();
   for (Map.Entry<Symbol, Expression> assignment : assignments.entrySet()) {
     if (assignment.getValue() instanceof SymbolReference) {
       inputToOutput.put(Symbol.from(assignment.getValue()), assignment.getKey());
     }
   }
   return inputToOutput;
 }
    @Override
    public ActualProperties visitProject(ProjectNode node, List<ActualProperties> inputProperties) {
      ActualProperties properties = Iterables.getOnlyElement(inputProperties);

      Map<Symbol, Symbol> identities = computeIdentityTranslations(node.getAssignments());

      ActualProperties translatedProperties =
          properties.translate(column -> Optional.ofNullable(identities.get(column)));

      // Extract additional constants
      Map<Symbol, NullableValue> constants = new HashMap<>();
      for (Map.Entry<Symbol, Expression> assignment : node.getAssignments().entrySet()) {
        Expression expression = assignment.getValue();

        IdentityHashMap<Expression, Type> expressionTypes =
            getExpressionTypes(
                session,
                metadata,
                parser,
                types,
                expression,
                emptyList() /* parameters already replaced */);
        Type type = requireNonNull(expressionTypes.get(expression));
        ExpressionInterpreter optimizer =
            ExpressionInterpreter.expressionOptimizer(
                expression, metadata, session, expressionTypes);
        // TODO:
        // We want to use a symbol resolver that looks up in the constants from the input subplan
        // to take advantage of constant-folding for complex expressions
        // However, that currently causes errors when those expressions operate on arrays or row
        // types
        // ("ROW comparison not supported for fields with null elements", etc)
        Object value = optimizer.optimize(NoOpSymbolResolver.INSTANCE);

        if (value instanceof SymbolReference) {
          Symbol symbol = Symbol.from((SymbolReference) value);
          NullableValue existingConstantValue = constants.get(symbol);
          if (existingConstantValue != null) {
            constants.put(assignment.getKey(), new NullableValue(type, value));
          }
        } else if (!(value instanceof Expression)) {
          constants.put(assignment.getKey(), new NullableValue(type, value));
        }
      }
      constants.putAll(translatedProperties.getConstants());

      return ActualProperties.builderFrom(translatedProperties).constants(constants).build();
    }