/*
 * Copyright 2015-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.facebook.buck.query;

import com.facebook.buck.query.QueryEnvironment.Argument;
import com.facebook.buck.query.QueryEnvironment.ArgumentType;
import com.facebook.buck.query.QueryEnvironment.QueryFunction;
import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.function.Predicate;

/**
 * A attrfilter(attribute, value, argument) filter expression, which computes the subset of nodes in
 * 'argument' whose 'attribute' contains the given value.
 *
 * <pre>expr ::= ATTRFILTER '(' WORD ',' WORD ',' expr ')'</pre>
 */
public class AttrFilterFunction implements QueryFunction {

  private static final ImmutableList<ArgumentType> ARGUMENT_TYPES =
      ImmutableList.of(ArgumentType.WORD, ArgumentType.WORD, ArgumentType.EXPRESSION);

  public AttrFilterFunction() {}

  @Override
  public String getName() {
    return "attrfilter";
  }

  @Override
  public int getMandatoryArguments() {
    return 3;
  }

  @Override
  public ImmutableList<ArgumentType> getArgumentTypes() {
    return ARGUMENT_TYPES;
  }

  @Override
  public ImmutableSet<QueryTarget> eval(
      QueryEvaluator evaluator, QueryEnvironment env, ImmutableList<Argument> args)
      throws QueryException {
    QueryExpression argument = args.get(args.size() - 1).getExpression();
    String attr = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, args.get(0).getWord());

    String attrValue = args.get(1).getWord();
    Predicate<Object> predicate = input -> attrValue.equals(input.toString());

    ImmutableSet.Builder<QueryTarget> result = new ImmutableSet.Builder<>();
    for (QueryTarget target : evaluator.eval(argument, env)) {
      ImmutableSet<Object> matchingObjects = env.filterAttributeContents(target, attr, predicate);
      if (!matchingObjects.isEmpty()) {
        result.add(target);
      }
    }
    return result.build();
  }
}

@@ -954,16 +954,67 @@
 bleSet;%0A
+import java.util.Collection;%0Aimport java.util.Map;%0A
 import j
@@ -2151,24 +2151,348 @@
 .getWord();%0A
+    // filterAttributeContents() below will traverse the entire type hierarchy of each attr (see the%0A    // various type coercers). Collection types are (1) very common (2) expensive to convert to%0A    // string and (3) we shouldn't apply the filter to the stringified form, and so we have a fast%0A    // path to ignore them.%0A
     Predicat
@@ -2512,16 +2512,24 @@
 dicate =
+%0A       
  input -
@@ -2529,16 +2529,102 @@
 input -%3E
+%0A            !(input instanceof Collection %7C%7C input instanceof Map)%0A                &&
  attrVal

Add a fast path to ignore collection types for AttrFilterFunction