diff options
author | 2015-04-22 06:47:31 +0000 | |
---|---|---|
committer | 2015-04-22 12:42:37 +0000 | |
commit | 537a90b5b90856788412c962de61ece14e83274b (patch) | |
tree | 08b489df7f200b6fa59fee0adc857245f78f88e2 /src/main/java | |
parent | 443aaae842aaf5009c2972277a1089c504873877 (diff) |
Use BuiltinFunction for all builtins
Replace the uses of AbstractFunction, MixedModeFunction,
SkylarkFunction and SimpleSkylarkFunction by BuiltinFunction.
--
MOS_MIGRATED_REVID=91763158
Diffstat (limited to 'src/main/java')
12 files changed, 972 insertions, 1030 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java index 07988a9374..9d7faa82da 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java @@ -14,19 +14,15 @@ package com.google.devtools.build.lib.packages; -import static com.google.devtools.build.lib.syntax.SkylarkFunction.cast; - import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; -import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Type.ConversionException; -import com.google.devtools.build.lib.syntax.AbstractFunction; -import com.google.devtools.build.lib.syntax.AbstractFunction.NoArgFunction; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; import com.google.devtools.build.lib.syntax.DotExpression; @@ -35,7 +31,6 @@ import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Function; -import com.google.devtools.build.lib.syntax.MixedModeFunction; import com.google.devtools.build.lib.syntax.SelectorList; import com.google.devtools.build.lib.syntax.SelectorValue; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; @@ -44,11 +39,12 @@ import com.google.devtools.build.lib.syntax.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkNestedSet; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -106,7 +102,7 @@ public class MethodLibrary { return str.substring(start, stop); } - public static int getListIndex(Object key, int listSize, FuncallExpression ast) + public static int getListIndex(Object key, int listSize, Location loc) throws ConversionException, EvalException { // Get the nth element in the list int index = Type.INTEGER.convert(key, "index operand"); @@ -114,7 +110,7 @@ public class MethodLibrary { index += listSize; } if (index < 0 || index >= listSize) { - throw new EvalException(ast.getLocation(), "List index out of range (index is " + throw new EvalException(loc, "List index out of range (index is " + index + ", but list has " + listSize + " elements)"); } return index; @@ -129,25 +125,20 @@ public class MethodLibrary { mandatoryPositionals = { @Param(name = "self", type = String.class, doc = "This string, a separator."), @Param(name = "elements", type = HackHackEitherList.class, doc = "The objects to join.")}) - private static Function join = new MixedModeFunction("join", - ImmutableList.of("this", "elements"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - String thisString = Type.STRING.convert(args[0], "'join' operand"); - List<?> seq = Type.OBJECT_LIST.convert(args[1], "'join' argument"); - return Joiner.on(thisString).join(seq); - }}; + private static BuiltinFunction join = new BuiltinFunction("join") { + public String invoke(String self, Object elements) throws ConversionException { + List<?> seq = Type.OBJECT_LIST.convert(elements, "'join.elements' argument"); + return Joiner.on(self).join(seq); + } + }; @SkylarkSignature(name = "lower", objectType = StringModule.class, returnType = String.class, doc = "Returns the lower case version of this string.", mandatoryPositionals = { @Param(name = "self", type = String.class, doc = "This string, to convert to lower case.")}) - private static Function lower = new MixedModeFunction("lower", - ImmutableList.of("this"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - String thiz = Type.STRING.convert(args[0], "'lower' operand"); - return thiz.toLowerCase(); + private static BuiltinFunction lower = new BuiltinFunction("lower") { + public String invoke(String self) { + return self.toLowerCase(); } }; @@ -155,12 +146,9 @@ public class MethodLibrary { doc = "Returns the upper case version of this string.", mandatoryPositionals = { @Param(name = "self", type = String.class, doc = "This string, to convert to upper case.")}) - private static Function upper = new MixedModeFunction("upper", - ImmutableList.of("this"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - String thiz = Type.STRING.convert(args[0], "'upper' operand"); - return thiz.toUpperCase(); + private static BuiltinFunction upper = new BuiltinFunction("upper") { + public String invoke(String self) { + return self.toUpperCase(); } }; @@ -176,26 +164,20 @@ public class MethodLibrary { @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", doc = "The maximum number of replacements.")}, useLocation = true) - private static Function replace = - new MixedModeFunction("replace", ImmutableList.of("this", "old", "new", "maxsplit"), 3, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { - String thiz = Type.STRING.convert(args[0], "'replace' operand"); - String old = Type.STRING.convert(args[1], "'replace' argument"); - String neww = Type.STRING.convert(args[2], "'replace' argument"); - int maxsplit = - args[3] != null ? Type.INTEGER.convert(args[3], "'replace' argument") - : Integer.MAX_VALUE; + private static BuiltinFunction replace = new BuiltinFunction("replace") { + public String invoke(String self, String oldString, String newString, Object maxSplitO, + Location loc) throws EvalException, ConversionException { StringBuffer sb = new StringBuffer(); + Integer maxSplit = Type.INTEGER.convertOptional( + maxSplitO, "'maxsplit' argument of 'replace'", /*label*/null, Integer.MAX_VALUE); try { - Matcher m = Pattern.compile(old, Pattern.LITERAL).matcher(thiz); - for (int i = 0; i < maxsplit && m.find(); i++) { - m.appendReplacement(sb, Matcher.quoteReplacement(neww)); + Matcher m = Pattern.compile(oldString, Pattern.LITERAL).matcher(self); + for (int i = 0; i < maxSplit && m.find(); i++) { + m.appendReplacement(sb, Matcher.quoteReplacement(newString)); } m.appendTail(sb); } catch (IllegalStateException e) { - throw new EvalException(ast.getLocation(), e.getMessage() + " in call to replace"); + throw new EvalException(loc, e.getMessage() + " in call to replace"); } return sb.toString(); } @@ -213,20 +195,14 @@ public class MethodLibrary { @Param(name = "maxsplit", type = Integer.class, noneable = true, defaultValue = "None", doc = "The maximum number of splits.")}, useEnvironment = true) - private static Function split = new MixedModeFunction("split", - ImmutableList.of("this", "sep", "maxsplit"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) - throws ConversionException { - String thiz = Type.STRING.convert(args[0], "'split' operand"); - String sep = args[1] != null - ? Type.STRING.convert(args[1], "'split' argument") - : " "; - int maxsplit = args[2] != null - ? Type.INTEGER.convert(args[2], "'split' argument") + 1 // last is remainder - : -1; - String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(thiz, maxsplit); - List<String> result = java.util.Arrays.asList(ss); + private static BuiltinFunction split = new BuiltinFunction("split") { + public Object invoke(String self, String sep, Object maxSplitO, + Environment env) throws ConversionException { + int maxSplit = Type.INTEGER.convertOptional( + maxSplitO, "'split' argument of 'split'", /*label*/null, -2); + // + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1 + String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1); + List<String> result = Arrays.<String>asList(ss); return env.isSkylarkEnabled() ? SkylarkList.list(result, String.class) : result; } }; @@ -244,19 +220,6 @@ public class MethodLibrary { return subpos < 0 ? subpos : subpos + start; } - // Common implementation for find, rfind, index, rindex. - // forward is true iff we want to return the last matching index. - private static int stringFind(String functionName, boolean forward, Object[] args) - throws ConversionException { - String thiz = Type.STRING.convert(args[0], functionName + " operand"); - String sub = Type.STRING.convert(args[1], functionName + " argument"); - int start = 0; - if (!EvalUtils.isNullOrNone(args[2])) { - start = Type.INTEGER.convert(args[2], functionName + " argument"); - } - return stringFind(forward, thiz, sub, start, args[3], functionName + " argument"); - } - @SkylarkSignature(name = "rfind", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the last index where <code>sub</code> is found, " + "or -1 if no such index exists, optionally restricting to " @@ -270,13 +233,12 @@ public class MethodLibrary { doc = "Restrict to search from this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}) - private static Function rfind = - new MixedModeFunction("rfind", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - return stringFind("rfind", false, args); - } - }; + private static BuiltinFunction rfind = new BuiltinFunction("rfind") { + public Integer invoke(String self, String sub, Integer start, Object end) + throws ConversionException { + return stringFind(false, self, sub, start, end, "'end' argument to rfind"); + } + }; @SkylarkSignature(name = "find", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the first index where <code>sub</code> is found, " @@ -291,14 +253,12 @@ public class MethodLibrary { doc = "Restrict to search from this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}) - private static Function find = - new MixedModeFunction("find", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws ConversionException { - return stringFind("find", true, args); - } - }; + private static BuiltinFunction find = new BuiltinFunction("find") { + public Integer invoke(String self, String sub, Integer start, Object end) + throws ConversionException { + return stringFind(true, self, sub, start, end, "'end' argument to find"); + } + }; @SkylarkSignature(name = "rindex", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the last index where <code>sub</code> is found, " @@ -314,20 +274,17 @@ public class MethodLibrary { @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}, useLocation = true) - private static Function rindex = - new MixedModeFunction("rindex", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws EvalException, ConversionException { - int res = stringFind("rindex", false, args); - if (res < 0) { - throw new EvalException(ast.getLocation(), - "substring " + EvalUtils.prettyPrintValue(args[1]) - + " not found in " + EvalUtils.prettyPrintValue(args[0])); - } - return res; - } - }; + private static BuiltinFunction rindex = new BuiltinFunction("rindex") { + public Integer invoke(String self, String sub, Integer start, Object end, + Location loc) throws EvalException, ConversionException { + int res = stringFind(false, self, sub, start, end, "'end' argument to rindex"); + if (res < 0) { + throw new EvalException(loc, String.format("substring %s not found in %s", + EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self))); + } + return res; + } + }; @SkylarkSignature(name = "index", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the first index where <code>sub</code> is found, " @@ -343,20 +300,17 @@ public class MethodLibrary { @Param(name = "end", type = Integer.class, noneable = true, doc = "optional position before which to restrict to search.")}, useLocation = true) - private static Function index = - new MixedModeFunction("index", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws EvalException, ConversionException { - int res = stringFind("index", true, args); - if (res < 0) { - throw new EvalException(ast.getLocation(), - "substring " + EvalUtils.prettyPrintValue(args[1]) - + " not found in " + EvalUtils.prettyPrintValue(args[0])); - } - return res; - } - }; + private static BuiltinFunction index = new BuiltinFunction("index") { + public Integer invoke(String self, String sub, Integer start, Object end, + Location loc) throws EvalException, ConversionException { + int res = stringFind(true, self, sub, start, end, "'end' argument to index"); + if (res < 0) { + throw new EvalException(loc, String.format("substring %s not found in %s", + EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self))); + } + return res; + } + }; @SkylarkSignature(name = "count", objectType = StringModule.class, returnType = Integer.class, doc = "Returns the number of (non-overlapping) occurrences of substring <code>sub</code> in " @@ -370,30 +324,22 @@ public class MethodLibrary { doc = "Restrict to search from this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position before which to restrict to search.")}) - private static Function count = - new MixedModeFunction("count", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws ConversionException { - String thiz = Type.STRING.convert(args[0], "'count' operand"); - String sub = Type.STRING.convert(args[1], "'count' argument"); - int start = 0; - if (args[2] != null) { - start = Type.INTEGER.convert(args[2], "'count' argument"); - } - String str = pythonSubstring(thiz, start, args[3], "'end' argument to 'count'"); - if (sub.isEmpty()) { - return str.length() + 1; - } - int count = 0; - int index = -1; - while ((index = str.indexOf(sub)) >= 0) { - count++; - str = str.substring(index + sub.length()); - } - return count; - } - }; + private static BuiltinFunction count = new BuiltinFunction("count") { + public Integer invoke(String self, String sub, Integer start, Object end) + throws ConversionException { + String str = pythonSubstring(self, start, end, "'end' operand of 'find'"); + if (sub.isEmpty()) { + return str.length() + 1; + } + int count = 0; + int index = -1; + while ((index = str.indexOf(sub)) >= 0) { + count++; + str = str.substring(index + sub.length()); + } + return count; + } + }; @SkylarkSignature(name = "endswith", objectType = StringModule.class, returnType = Boolean.class, doc = "Returns True if the string ends with <code>sub</code>, " @@ -407,21 +353,13 @@ public class MethodLibrary { doc = "Test beginning at this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional position at which to stop comparing.")}) - private static Function endswith = - new MixedModeFunction("endswith", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws ConversionException { - String thiz = Type.STRING.convert(args[0], "'endswith' operand"); - String sub = Type.STRING.convert(args[1], "'endswith' argument"); - int start = 0; - if (args[2] != null) { - start = Type.INTEGER.convert(args[2], "'endswith' argument"); - } - return pythonSubstring(thiz, start, args[3], "'end' argument to 'endswith'") - .endsWith(sub); - } - }; + private static BuiltinFunction endswith = new BuiltinFunction( + "endswith", SkylarkList.tuple(0, Environment.NONE)) { + public Boolean invoke(String self, String sub, Integer start, Object end) + throws ConversionException { + return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub); + } + }; @SkylarkSignature(name = "startswith", objectType = StringModule.class, returnType = Boolean.class, @@ -436,18 +374,10 @@ public class MethodLibrary { doc = "Test beginning at this position."), @Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None", doc = "Stop comparing at this position.")}) - private static Function startswith = - new MixedModeFunction("startswith", ImmutableList.of("this", "sub", "start", "end"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - String thiz = Type.STRING.convert(args[0], "'startswith' operand"); - String sub = Type.STRING.convert(args[1], "'startswith' argument"); - int start = 0; - if (args[2] != null) { - start = Type.INTEGER.convert(args[2], "'startswith' argument"); - } - return pythonSubstring(thiz, start, args[3], "'end' argument to 'startswith'") - .startsWith(sub); + private static BuiltinFunction startswith = new BuiltinFunction("startswith") { + public Boolean invoke(String self, String sub, Integer start, Object end) + throws ConversionException { + return pythonSubstring(self, start, end, "'end' operand of 'startswith'").startsWith(sub); } }; @@ -457,15 +387,11 @@ public class MethodLibrary { + "have been stripped from the beginning and the end of the string.", mandatoryPositionals = { @Param(name = "self", type = String.class, doc = "This string.")}) - private static Function strip = - new MixedModeFunction("strip", ImmutableList.of("this"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws ConversionException { - String operand = Type.STRING.convert(args[0], "'strip' operand"); - return operand.trim(); - } - }; + private static BuiltinFunction strip = new BuiltinFunction("strip") { + public String invoke(String self) { + return self.trim(); + } + }; // slice operator @SkylarkSignature(name = "$slice", documented = false, @@ -475,30 +401,22 @@ public class MethodLibrary { @Param(name = "end", type = Integer.class, doc = "end position of the slice.")}, doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.", useLocation = true, useEnvironment = true) - private static Function slice = new MixedModeFunction("$slice", - ImmutableList.of("this", "start", "end"), 3, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - int left = Type.INTEGER.convert(args[1], "start operand"); - int right = Type.INTEGER.convert(args[2], "end operand"); - + private static BuiltinFunction slice = new BuiltinFunction("$slice") { + public Object invoke(Object self, Integer left, Integer right, + Location loc, Environment env) throws EvalException, ConversionException { // Substring - if (args[0] instanceof String) { - String thiz = Type.STRING.convert(args[0], "substring operand"); - return pythonSubstring(thiz, left, right, ""); + if (self instanceof String) { + return pythonSubstring((String) self, left, right, ""); } // List slice - List<Object> list = Type.OBJECT_LIST.convert(args[0], "list operand"); + List<Object> list = Type.OBJECT_LIST.convert(self, "list operand"); left = clampIndex(left, list.size()); right = clampIndex(right, list.size()); - - List<Object> result = Lists.newArrayList(); - for (int i = left; i < right; i++) { - result.add(list.get(i)); + if (left > right) { + left = right; } - return convert(result, env, ast.getLocation()); + return convert(list.subList(left, right), env, loc); } }; @@ -508,18 +426,16 @@ public class MethodLibrary { mandatoryPositionals = { @Param(name = "self", type = HackHackEitherList.class, doc = "This list.")}, useLocation = true, useEnvironment = true) - private static Function sorted = new MixedModeFunction("sorted", - ImmutableList.of("self"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) + private static BuiltinFunction sorted = new BuiltinFunction("sorted") { + public Object invoke(Object self, Location loc, Environment env) throws EvalException, ConversionException { - List<Object> self = Type.OBJECT_LIST.convert(args[0], "'sorted' operand"); + List<Object> list = Type.OBJECT_LIST.convert(self, "'sorted' operand"); try { - self = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(self); + list = Ordering.from(EvalUtils.SKYLARK_COMPARATOR).sortedCopy(list); } catch (EvalUtils.ComparisonException e) { - throw new EvalException(ast.getLocation(), e); + throw new EvalException(loc, e); } - return convert(self, env, ast.getLocation()); + return convert(list, env, loc); } }; @@ -531,13 +447,10 @@ public class MethodLibrary { @Param(name = "self", type = List.class, doc = "This list."), @Param(name = "item", type = Object.class, doc = "Item to add at the end.")}, useLocation = true, useEnvironment = true) - private static Function append = new MixedModeFunction("append", - ImmutableList.of("this", "x"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { - List<Object> thiz = Type.OBJECT_LIST.convert(args[0], "'append' operand"); - thiz.add(args[1]); + private static BuiltinFunction append = new BuiltinFunction("append") { + public Environment.NoneType invoke(List<Object> self, Object item, + Location loc, Environment env) throws EvalException, ConversionException { + self.add(item); return Environment.NONE; } }; @@ -550,14 +463,10 @@ public class MethodLibrary { @Param(name = "self", type = List.class, doc = "This list."), @Param(name = "items", type = List.class, doc = "Items to add at the end.")}, useLocation = true, useEnvironment = true) - private static Function extend = new MixedModeFunction("extend", - ImmutableList.of("this", "x"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { - List<Object> thiz = Type.OBJECT_LIST.convert(args[0], "'extend' operand"); - List<Object> l = Type.OBJECT_LIST.convert(args[1], "'extend' argument"); - thiz.addAll(l); + private static BuiltinFunction extend = new BuiltinFunction("extend") { + public Environment.NoneType invoke(List<Object> self, List<Object> items, + Location loc, Environment env) throws EvalException, ConversionException { + self.addAll(items); return Environment.NONE; } }; @@ -570,50 +479,43 @@ public class MethodLibrary { @Param(name = "self", type = Object.class, doc = "This object."), @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, useLocation = true) - private static Function indexOperator = new MixedModeFunction("$index", - ImmutableList.of("this", "index"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { - Object collectionCandidate = args[0]; - Object key = args[1]; - - if (collectionCandidate instanceof Map<?, ?>) { - Map<?, ?> dictionary = (Map<?, ?>) collectionCandidate; - if (!dictionary.containsKey(key)) { - throw new EvalException(ast.getLocation(), - "Key " + EvalUtils.prettyPrintValue(key) + " not found in dictionary"); + private static BuiltinFunction indexOperator = new BuiltinFunction("$index") { + public Object invoke(Object self, Object key, + Location loc) throws EvalException, ConversionException { + if (self instanceof SkylarkList) { + SkylarkList list = (SkylarkList) self; + if (list.isEmpty()) { + throw new EvalException(loc, "List is empty"); } - return dictionary.get(key); - } else if (collectionCandidate instanceof List<?>) { + int index = getListIndex(key, list.size(), loc); + return list.get(index); - List<Object> list = Type.OBJECT_LIST.convert(collectionCandidate, "index operand"); - - if (!list.isEmpty()) { - int index = getListIndex(key, list.size(), ast); - return list.get(index); + } else if (self instanceof Map<?, ?>) { + Map<?, ?> dictionary = (Map<?, ?>) self; + if (!dictionary.containsKey(key)) { + throw new EvalException(loc, String.format("Key %s not found in dictionary", + EvalUtils.prettyPrintValue(key))); } + return dictionary.get(key); - throw new EvalException(ast.getLocation(), "List is empty"); - } else if (collectionCandidate instanceof SkylarkList) { - SkylarkList list = (SkylarkList) collectionCandidate; - - if (!list.isEmpty()) { - int index = getListIndex(key, list.size(), ast); - return list.get(index); + } else if (self instanceof List<?>) { + List<Object> list = Type.OBJECT_LIST.convert(self, "index operand"); + if (list.isEmpty()) { + throw new EvalException(loc, "List is empty"); } + int index = getListIndex(key, list.size(), loc); + return list.get(index); - throw new EvalException(ast.getLocation(), "List is empty"); - } else if (collectionCandidate instanceof String) { - String str = (String) collectionCandidate; - int index = getListIndex(key, str.length(), ast); + } else if (self instanceof String) { + String str = (String) self; + int index = getListIndex(key, str.length(), loc); return str.substring(index, index + 1); } else { // TODO(bazel-team): This is dead code, get rid of it. - throw new EvalException(ast.getLocation(), String.format( + throw new EvalException(loc, String.format( "Unsupported datatype (%s) for indexing, only works for dict and list", - EvalUtils.getDataTypeName(collectionCandidate))); + EvalUtils.getDataTypeName(self))); } } }; @@ -625,13 +527,12 @@ public class MethodLibrary { + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n", mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")}, useLocation = true, useEnvironment = true) - private static Function values = new NoArgFunction("values") { - @Override - public Object call(Object self, FuncallExpression ast, Environment env) - throws EvalException, InterruptedException { + private static BuiltinFunction values = new BuiltinFunction("values") { + public Object invoke(Map<?, ?> self, + Location loc, Environment env) throws EvalException, ConversionException { // Use a TreeMap to ensure consistent ordering. - Map<?, ?> dict = new TreeMap<>((Map<?, ?>) self); - return convert(dict.values(), env, ast.getLocation()); + Map<?, ?> dict = new TreeMap<>(self); + return convert(dict.values(), env, loc); } }; @@ -644,20 +545,19 @@ public class MethodLibrary { mandatoryPositionals = { @Param(name = "self", type = Map.class, doc = "This dict.")}, useLocation = true, useEnvironment = true) - private static Function items = new NoArgFunction("items") { - @Override - public Object call(Object self, FuncallExpression ast, Environment env) - throws EvalException, InterruptedException { + private static BuiltinFunction items = new BuiltinFunction("items") { + public Object invoke(Map<?, ?> self, + Location loc, Environment env) throws EvalException, ConversionException { // Use a TreeMap to ensure consistent ordering. - Map<?, ?> dict = new TreeMap<>((Map<?, ?>) self); + Map<?, ?> dict = new TreeMap<>(self); List<Object> list = Lists.newArrayListWithCapacity(dict.size()); for (Map.Entry<?, ?> entries : dict.entrySet()) { List<?> item = ImmutableList.of(entries.getKey(), entries.getValue()); list.add(env.isSkylarkEnabled() ? SkylarkList.tuple(item) : item); } - return convert(list, env, ast.getLocation()); - } - }; + return convert(list, env, loc); + } + }; @SkylarkSignature(name = "keys", objectType = DictModule.class, returnType = HackHackEitherList.class, @@ -667,14 +567,12 @@ public class MethodLibrary { mandatoryPositionals = { @Param(name = "self", type = Map.class, doc = "This dict.")}, useLocation = true, useEnvironment = true) - private static Function keys = new NoArgFunction("keys") { - @Override - @SuppressWarnings("unchecked") // Skylark will only call this on a dict; and - // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception. - public Object call(Object self, FuncallExpression ast, Environment env) - throws EvalException, InterruptedException { - Map<Comparable<?>, Object> dict = (Map<Comparable<?>, Object>) self; - return convert(Ordering.natural().sortedCopy(dict.keySet()), env, ast.getLocation()); + // Skylark will only call this on a dict; and + // allowed keys are all Comparable... if not mutually, it's OK to get a runtime exception. + private static BuiltinFunction keys = new BuiltinFunction("keys") { + public Object invoke(Map<Comparable<?>, ?> dict, + Location loc, Environment env) throws EvalException { + return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc); } }; @@ -688,19 +586,12 @@ public class MethodLibrary { optionalPositionals = { @Param(name = "default", defaultValue = "None", doc = "The default value to use (instead of None) if the key is not found.")}) - private static Function get = - new MixedModeFunction("get", ImmutableList.of("this", "key", "default"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - Map<?, ?> dict = (Map<?, ?>) args[0]; - Object key = args[1]; - if (dict.containsKey(key)) { - return dict.get(key); - } - if (args[2] == null) { - return Environment.NONE; + private static BuiltinFunction get = new BuiltinFunction("get") { + public Object invoke(Map<?, ?> self, Object key, Object defaultValue) { + if (self.containsKey(key)) { + return self.get(key); } - return args[2]; + return defaultValue; } }; @@ -716,13 +607,12 @@ public class MethodLibrary { } // unary minus - @SkylarkSignature(name = "-", documented = false, doc = "Unary minus operator.", + @SkylarkSignature(name = "-", returnType = Integer.class, + documented = false, doc = "Unary minus operator.", mandatoryPositionals = { @Param(name = "num", type = Integer.class, doc = "The number to negate.")}) - private static Function minus = new MixedModeFunction("-", ImmutableList.of("this"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws ConversionException { - int num = Type.INTEGER.convert(args[0], "'unary minus' argument"); + private static BuiltinFunction minus = new BuiltinFunction("-") { + public Integer invoke(Integer num) throws ConversionException { return -num; } }; @@ -734,12 +624,9 @@ public class MethodLibrary { + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>", mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}, useLocation = true) - private static Function list = new MixedModeFunction("list", - ImmutableList.of("list"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException { - Location loc = ast.getLocation(); - return SkylarkList.list(EvalUtils.toCollection(args[0], loc), loc); + private static BuiltinFunction list = new BuiltinFunction("list") { + public SkylarkList invoke(Object x, Location loc) throws EvalException { + return SkylarkList.list(EvalUtils.toCollection(x, loc), loc); } }; @@ -747,16 +634,11 @@ public class MethodLibrary { "Returns the length of a string, list, tuple, set, or dictionary.", mandatoryPositionals = {@Param(name = "x", doc = "The object to check length of.")}, useLocation = true) - private static Function len = new MixedModeFunction("len", - ImmutableList.of("list"), 1, false) { - - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException { - Object arg = args[0]; - int l = EvalUtils.size(arg); + private static BuiltinFunction len = new BuiltinFunction("len") { + public Integer invoke(Object x, Location loc) throws EvalException { + int l = EvalUtils.size(x); if (l == -1) { - throw new EvalException(ast.getLocation(), - EvalUtils.getDataTypeName(arg) + " is not iterable"); + throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable"); } return l; } @@ -765,10 +647,9 @@ public class MethodLibrary { @SkylarkSignature(name = "str", returnType = String.class, doc = "Converts any object to string. This is useful for debugging.", mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) - private static Function str = new MixedModeFunction("str", ImmutableList.of("this"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException { - return EvalUtils.printValue(args[0]); + private static BuiltinFunction str = new BuiltinFunction("str") { + public String invoke(Object x) throws EvalException { + return EvalUtils.printValue(x); } }; @@ -778,11 +659,9 @@ public class MethodLibrary { + "empty collection. Otherwise, it returns True. Similarly to Python <code>bool</code> " + "is also a type.", mandatoryPositionals = {@Param(name = "x", doc = "The variable to convert.")}) - private static Function bool = new MixedModeFunction("bool", - ImmutableList.of("this"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException { - return EvalUtils.toBoolean(args[0]); + private static BuiltinFunction bool = new BuiltinFunction("bool") { + public Boolean invoke(Object x) throws EvalException { + return EvalUtils.toBoolean(x); } }; @@ -792,20 +671,16 @@ public class MethodLibrary { mandatoryPositionals = { @Param(name = "x", type = String.class, doc = "The string to convert.")}, useLocation = true) - private static Function int_ = - new MixedModeFunction("int", ImmutableList.of("x"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws EvalException, ConversionException { - String str = Type.STRING.convert(args[0], "'int' operand"); - try { - return Integer.parseInt(str); - } catch (NumberFormatException e) { - throw new EvalException(ast.getLocation(), - "invalid literal for int(): " + EvalUtils.prettyPrintValue(str)); - } - } - }; + private static BuiltinFunction int_ = new BuiltinFunction("int") { + public Integer invoke(String x, Location loc) throws EvalException { + try { + return Integer.parseInt(x); + } catch (NumberFormatException e) { + throw new EvalException(loc, + "invalid literal for int(): " + EvalUtils.prettyPrintValue(x)); + } + } + }; @SkylarkSignature(name = "struct", returnType = SkylarkClassObject.class, doc = "Creates an immutable struct using the keyword arguments as fields. It is used to group " @@ -815,14 +690,11 @@ public class MethodLibrary { extraKeywords = { @Param(name = "kwarg", doc = "the struct fields")}, useLocation = true) - private static Function struct = new AbstractFunction("struct") { - @Override - public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast, - Environment env) throws EvalException, InterruptedException { - if (!args.isEmpty()) { - throw new EvalException(ast.getLocation(), "struct only supports keyword arguments"); - } - return new SkylarkClassObject(kwargs, ast.getLocation()); + private static BuiltinFunction struct = new BuiltinFunction("struct") { + @SuppressWarnings("unchecked") + public SkylarkClassObject invoke(Map<String, Object> kwargs, Location loc) + throws EvalException, InterruptedException { + return new SkylarkClassObject(kwargs, loc); } }; @@ -841,16 +713,10 @@ public class MethodLibrary { + "possible values are: <code>stable</code> (default), <code>compile</code>, " + "<code>link</code> or <code>naive_link</code>.")}, useLocation = true) - private static final Function set = - new MixedModeFunction("set", ImmutableList.of("items", "order"), 0, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { - Order order = SkylarkNestedSet.parseOrder((String) args[1], ast.getLocation()); - if (args[0] == null) { - return new SkylarkNestedSet(order, SkylarkList.EMPTY_LIST, ast.getLocation()); - } - return new SkylarkNestedSet(order, args[0], ast.getLocation()); + private static final BuiltinFunction set = new BuiltinFunction("set") { + public SkylarkNestedSet invoke(Object items, String order, + Location loc) throws EvalException, ConversionException { + return new SkylarkNestedSet(SkylarkNestedSet.parseOrder(order, loc), items, loc); } }; @@ -860,21 +726,16 @@ public class MethodLibrary { + "enumerate([24, 21, 84]) == [[0, 24], [1, 21], [2, 84]]</pre>\n", mandatoryPositionals = {@Param(name = "list", type = SkylarkList.class, doc = "input list")}, useLocation = true) - private static Function enumerate = new MixedModeFunction("enumerate", - ImmutableList.of("list"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { - // Note that enumerate is only available in Skylark. - SkylarkList input = cast( - args[0], SkylarkList.class, "enumerate operand", ast.getLocation()); - List<SkylarkList> result = Lists.newArrayList(); + private static BuiltinFunction enumerate = new BuiltinFunction("enumerate") { + public SkylarkList invoke(SkylarkList input, Location loc) + throws EvalException, ConversionException, InterruptedException { int count = 0; + List<SkylarkList> result = Lists.newArrayList(); for (Object obj : input) { - result.add(SkylarkList.tuple(Lists.newArrayList(count, obj))); + result.add(SkylarkList.tuple(count, obj)); count++; } - return SkylarkList.list(result, ast.getLocation()); + return SkylarkList.list(result, loc); } }; @@ -886,34 +747,31 @@ public class MethodLibrary { + "range(3, 9, 2) == [3, 5, 7]\n" + "range(3, 0, -1) == [3, 2, 1]</pre>", mandatoryPositionals = { - @Param(name = "start", type = Integer.class, - doc = "Value of the first element if stop is provided, " + @Param(name = "start_or_stop", type = Integer.class, + doc = "Value of the start element if stop is provided, " + "otherwise value of stop and the actual start is 0"), }, optionalPositionals = { - @Param(name = "stop", type = Integer.class, noneable = true, defaultValue = "None", + @Param(name = "stop_or_none", type = Integer.class, noneable = true, defaultValue = "None", doc = "optional index of the first item <i>not</i> to be included in the " + "resulting list; generation of the list stops before <code>stop</code> is reached."), @Param(name = "step", type = Integer.class, defaultValue = "1", doc = "The increment (default is 1). It may be negative.")}, useLocation = true) - private static final Function range = - new MixedModeFunction("range", ImmutableList.of("start", "stop", "step"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException, - ConversionException { + private static final BuiltinFunction range = new BuiltinFunction("range") { + public SkylarkList invoke(Integer startOrStop, Object stopOrNone, Integer step, Location loc) + throws EvalException, ConversionException, InterruptedException { int start; int stop; - if (args[1] == null) { + if (stopOrNone == Environment.NONE) { start = 0; - stop = Type.INTEGER.convert(args[0], "stop"); + stop = startOrStop; } else { - start = Type.INTEGER.convert(args[0], "start"); - stop = Type.INTEGER.convert(args[1], "stop"); + start = startOrStop; + stop = Type.INTEGER.convert(stopOrNone, "'stop' operand of 'range'"); } - int step = args[2] == null ? 1 : Type.INTEGER.convert(args[2], "step"); if (step == 0) { - throw new EvalException(ast.getLocation(), "step cannot be 0"); + throw new EvalException(loc, "step cannot be 0"); } List<Integer> result = Lists.newArrayList(); if (step > 0) { @@ -939,19 +797,11 @@ public class MethodLibrary { doc = "Creates a SelectorValue from the dict parameter.", mandatoryPositionals = { @Param(name = "x", type = Map.class, doc = "The parameter to convert.")}) - private static final Function select = new MixedModeFunction("select", - ImmutableList.of("x"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws EvalException, ConversionException { - Object dict = args[0]; - if (!(dict instanceof Map<?, ?>)) { - throw new EvalException(ast.getLocation(), - "select({...}) argument isn't a dictionary"); - } - return SelectorList.of(new SelectorValue((Map<?, ?>) dict)); - } - }; + private static final BuiltinFunction select = new BuiltinFunction("select") { + public Object invoke(Map<?, ?> dict) throws EvalException, InterruptedException { + return SelectorList.of(new SelectorValue(dict)); + } + }; /** * Returns true if the object has a field of the given name, otherwise false. @@ -964,18 +814,12 @@ public class MethodLibrary { @Param(name = "object", doc = "The object to check."), @Param(name = "name", type = String.class, doc = "The name of the field.")}, useLocation = true, useEnvironment = true) - private static final Function hasattr = - new MixedModeFunction("hasattr", ImmutableList.of("object", "name"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - Object obj = args[0]; - String name = cast(args[1], String.class, "name", ast.getLocation()); - + private static final BuiltinFunction hasattr = new BuiltinFunction("hasattr") { + public Boolean invoke(Object obj, String name, + Location loc, Environment env) throws EvalException, ConversionException { if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) { return true; } - if (env.getFunctionNames(obj.getClass()).contains(name)) { return true; } @@ -984,7 +828,7 @@ public class MethodLibrary { return FuncallExpression.getMethodNames(obj.getClass()).contains(name); } catch (ExecutionException e) { // This shouldn't happen - throw new EvalException(ast.getLocation(), e.getMessage()); + throw new EvalException(loc, e.getMessage()); } } }; @@ -1004,20 +848,15 @@ public class MethodLibrary { doc = "The default value to return in case the struct " + "doesn't have a field of the given name.")}, useLocation = true) - private static final Function getattr = new MixedModeFunction( - "getattr", ImmutableList.of("object", "name", "default"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) - throws EvalException { - Object obj = args[0]; - String name = cast(args[1], String.class, "name", ast.getLocation()); - Object result = DotExpression.eval(obj, name, ast.getLocation()); + private static final BuiltinFunction getattr = new BuiltinFunction("getattr") { + public Object invoke(Object obj, String name, Object defaultValue, + Location loc) throws EvalException, ConversionException { + Object result = DotExpression.eval(obj, name, loc); if (result == null) { - if (args[2] != null) { - return args[2]; + if (defaultValue != Environment.NONE) { + return defaultValue; } else { - throw new EvalException(ast.getLocation(), - String.format("Object of type '%s' has no field %s", + throw new EvalException(loc, String.format("Object of type '%s' has no field %s", EvalUtils.getDataTypeName(obj), EvalUtils.prettyPrintValue(name))); } } @@ -1030,23 +869,20 @@ public class MethodLibrary { + "methods of the parameter object.", mandatoryPositionals = {@Param(name = "object", doc = "The object to check.")}, useLocation = true, useEnvironment = true) - private static final Function dir = new MixedModeFunction( - "dir", ImmutableList.of("object"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) - throws EvalException { - Object obj = args[0]; + private static final BuiltinFunction dir = new BuiltinFunction("dir") { + public SkylarkList invoke(Object object, + Location loc, Environment env) throws EvalException, ConversionException { // Order the fields alphabetically. Set<String> fields = new TreeSet<>(); - if (obj instanceof ClassObject) { - fields.addAll(((ClassObject) obj).getKeys()); + if (object instanceof ClassObject) { + fields.addAll(((ClassObject) object).getKeys()); } - fields.addAll(env.getFunctionNames(obj.getClass())); + fields.addAll(env.getFunctionNames(object.getClass())); try { - fields.addAll(FuncallExpression.getMethodNames(obj.getClass())); + fields.addAll(FuncallExpression.getMethodNames(object.getClass())); } catch (ExecutionException e) { // This shouldn't happen - throw new EvalException(ast.getLocation(), e.getMessage()); + throw new EvalException(loc, e.getMessage()); } return SkylarkList.list(fields, String.class); } @@ -1055,12 +891,10 @@ public class MethodLibrary { @SkylarkSignature(name = "type", returnType = String.class, doc = "Returns the type name of its argument.", mandatoryPositionals = {@Param(name = "object", doc = "The object to check type of.")}) - private static final Function type = new MixedModeFunction("type", - ImmutableList.of("object"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) throws EvalException { + private static final BuiltinFunction type = new BuiltinFunction("type") { + public String invoke(Object object) { // There is no 'type' type in Skylark, so we return a string with the type name. - return EvalUtils.getDataTypeName(args[0], false); + return EvalUtils.getDataTypeName(object, false); } }; @@ -1074,17 +908,13 @@ public class MethodLibrary { defaultValue = "None", doc = "The name of the attribute that caused the error")}, useLocation = true) - private static final Function fail = new MixedModeFunction( - "fail", ImmutableList.of("msg", "attr"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast, Environment env) - throws EvalException { - String msg = cast(args[0], String.class, "msg", ast.getLocation()); - if (args[1] != null) { - msg = "attribute " + cast(args[1], String.class, "attr", ast.getLocation()) - + ": " + msg; + private static final BuiltinFunction fail = new BuiltinFunction("fail") { + public Environment.NoneType invoke(String msg, Object attr, + Location loc) throws EvalException, ConversionException { + if (attr != Environment.NONE) { + msg = String.format("attribute %s: %s", attr, msg); } - throw new EvalException(ast.getLocation(), msg); + throw new EvalException(loc, msg); } }; @@ -1096,30 +926,16 @@ public class MethodLibrary { // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file' extraPositionals = {@Param(name = "args", doc = "The objects to print.")}, useLocation = true, useEnvironment = true) - private static final Function print = new AbstractFunction("print") { - @Override - public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast, - Environment env) throws EvalException, InterruptedException { - String sep = " "; - int count = 0; - if (kwargs.containsKey("sep")) { - sep = cast(kwargs.get("sep"), String.class, "sep", ast.getLocation()); - count = 1; - } - if (kwargs.size() > count) { - kwargs = new HashMap<>(kwargs); - kwargs.remove("sep"); - List<String> bad = Ordering.natural().sortedCopy(kwargs.keySet()); - throw new EvalException(ast.getLocation(), "unexpected keywords: '" + bad + "'"); - } - String msg = Joiner.on(sep).join(Iterables.transform(args, - new com.google.common.base.Function<Object, String>() { - @Override - public String apply(Object input) { - return EvalUtils.printValue(input); - } - })); - ((SkylarkEnvironment) env).handleEvent(Event.warn(ast.getLocation(), msg)); + private static final BuiltinFunction print = new BuiltinFunction("print") { + public Environment.NoneType invoke(String sep, SkylarkList starargs, + Location loc, SkylarkEnvironment env) throws EvalException { + String msg = Joiner.on(sep).join(Iterables.transform(starargs, + new com.google.common.base.Function<Object, String>() { + @Override + public String apply(Object input) { + return EvalUtils.printValue(input); + }})); + env.handleEvent(Event.warn(loc, msg)); return Environment.NONE; } }; @@ -1136,13 +952,12 @@ public class MethodLibrary { + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>", extraPositionals = {@Param(name = "args", doc = "lists to zip")}, returnType = SkylarkList.class, useLocation = true) - private static final Function zip = new AbstractFunction("zip") { - @Override - public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast, - Environment env) throws EvalException, InterruptedException { + private static final BuiltinFunction zip = new BuiltinFunction("zip") { + public SkylarkList invoke(SkylarkList args, Location loc) + throws EvalException, InterruptedException { Iterator<?>[] iterators = new Iterator<?>[args.size()]; for (int i = 0; i < args.size(); i++) { - iterators[i] = EvalUtils.toIterable(args.get(i), ast.getLocation()).iterator(); + iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator(); } List<SkylarkList> result = new ArrayList<SkylarkList>(); boolean allHasNext; @@ -1160,7 +975,7 @@ public class MethodLibrary { result.add(SkylarkList.tuple(elem)); } } while (allHasNext); - return SkylarkList.list(result, ast.getLocation()); + return SkylarkList.list(result, loc); } }; @@ -1209,18 +1024,21 @@ public class MethodLibrary { + "</pre>") public static final class DictModule {} - public static final List<Function> stringFunctions = ImmutableList.of( + public static final List<Function> stringFunctions = ImmutableList.<Function>of( count, endswith, find, index, join, lower, replace, rfind, rindex, slice, split, startswith, strip, upper); - public static final List<Function> listPureFunctions = ImmutableList.of(slice); + public static final List<Function> listPureFunctions = ImmutableList.<Function>of( + slice); - public static final List<Function> listFunctions = ImmutableList.of(append, extend); + public static final List<Function> listFunctions = ImmutableList.<Function>of( + append, extend); - public static final List<Function> dictFunctions = ImmutableList.of(items, get, keys, values); + public static final List<Function> dictFunctions = ImmutableList.<Function>of( + items, get, keys, values); - private static final List<Function> pureGlobalFunctions = - ImmutableList.of(bool, int_, len, minus, select, sorted, str); + private static final List<Function> pureGlobalFunctions = ImmutableList.<Function>of( + bool, int_, len, minus, select, sorted, str); private static final List<Function> skylarkGlobalFunctions = ImmutableList .<Function>builder() @@ -1273,4 +1091,8 @@ public class MethodLibrary { builtIn.add(function.getName()); } } + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(MethodLibrary.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java index ac669e64de..b4d4a6df17 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java @@ -30,21 +30,27 @@ import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.packages.GlobCache.BadGlobException; import com.google.devtools.build.lib.packages.License.DistributionType; import com.google.devtools.build.lib.packages.Type.ConversionException; -import com.google.devtools.build.lib.syntax.AbstractFunction; import com.google.devtools.build.lib.syntax.AssignmentStatement; +import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.BuildFileAST; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException; import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Expression; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Function; +import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Ident; import com.google.devtools.build.lib.syntax.Label; -import com.google.devtools.build.lib.syntax.MixedModeFunction; import com.google.devtools.build.lib.syntax.ParserInputSource; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; +import com.google.devtools.build.lib.syntax.SkylarkSignature; +import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList; import com.google.devtools.build.lib.syntax.Statement; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.Path; @@ -52,7 +58,6 @@ import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.UnixGlob; import java.io.IOException; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -149,8 +154,6 @@ public final class PackageFactory { Iterable<PackageArgument<?>> getPackageArguments(); } - private static final int EXCLUDE_DIR_DEFAULT = 1; - private static class DefaultVisibility extends PackageArgument<List<Label>> { private DefaultVisibility() { super("default_visibility", Type.LABEL_LIST); @@ -452,21 +455,38 @@ public final class PackageFactory { * @param async if true, start globs in the background but don't block on their completion. * Only use this for heuristic preloading. */ - private static Function newGlobFunction( - final PackageContext originalContext, final boolean async) { - List<String> params = ImmutableList.of("include", "exclude", "exclude_directories"); - return new MixedModeFunction("glob", params, 1, false) { - @Override - public Object call(Object[] namedArguments, FuncallExpression ast, Environment env) + @SkylarkSignature(name = "glob", objectType = Object.class, returnType = GlobList.class, + doc = "Returns a list of files that match glob search pattern", + mandatoryPositionals = { + @Param(name = "include", type = HackHackEitherList.class, generic1 = String.class, + doc = "a list of strings specifying patterns of files to include.")}, + optionalPositionals = { + @Param(name = "exclude", type = HackHackEitherList.class, generic1 = String.class, + defaultValue = "[]", + doc = "a list of strings specifying patterns of files to exclude."), + // TODO(bazel-team): migrate all existing code to use boolean instead? + @Param(name = "exclude_directories", type = Integer.class, defaultValue = "1", + doc = "a integer that if non-zero indicates directories should not be matched.")}, + documented = false, useAst = true, useEnvironment = true) + private static final BuiltinFunction.Factory newGlobFunction = + new BuiltinFunction.Factory("glob") { + public BuiltinFunction create(final PackageContext originalContext, final boolean async) { + return new BuiltinFunction("glob", this) { + public GlobList<String> invoke( + Object include, Object exclude, Integer excludeDirectories, + FuncallExpression ast, Environment env) throws EvalException, ConversionException, InterruptedException { - return callGlob(originalContext, async, ast, env, namedArguments); + return callGlob( + originalContext, async, include, exclude, excludeDirectories != 0, ast, env); + } + }; } }; - } - static GlobList<String> callGlob(@Nullable PackageContext originalContext, boolean async, - FuncallExpression ast, Environment env, Object[] namedArguments) - throws EvalException, ConversionException, InterruptedException { + protected static GlobList<String> callGlob(@Nullable PackageContext originalContext, + boolean async, Object include, Object exclude, boolean excludeDirs, + FuncallExpression ast, Environment env) + throws EvalException, ConversionException, InterruptedException { // Skylark build extensions need to get the PackageContext from the Environment; // async glob functions cannot do the same because the Environment is not thread safe. PackageContext context; @@ -477,23 +497,18 @@ public final class PackageFactory { context = originalContext; } - List<String> includes = Type.STRING_LIST.convert(namedArguments[0], "'glob' argument"); - List<String> excludes = namedArguments[1] == null - ? Collections.<String>emptyList() - : Type.STRING_LIST.convert(namedArguments[1], "'glob' argument"); - int excludeDirs = namedArguments[2] == null - ? EXCLUDE_DIR_DEFAULT - : Type.INTEGER.convert(namedArguments[2], "'glob' argument"); + List<String> includes = Type.STRING_LIST.convert(include, "'glob' argument"); + List<String> excludes = Type.STRING_LIST.convert(exclude, "'glob' argument"); if (async) { try { - context.globber.runAsync(includes, excludes, excludeDirs != 0); + context.globber.runAsync(includes, excludes, excludeDirs); } catch (GlobCache.BadGlobException e) { // Ignore: errors will appear during the actual evaluation of the package. } return GlobList.captureResults(includes, excludes, ImmutableList.<String>of()); } else { - return handleGlob(includes, excludes, excludeDirs != 0, context, ast); + return handleGlob(includes, excludes, excludeDirs, context, ast); } } @@ -531,44 +546,57 @@ public final class PackageFactory { * seen by the parser, because the presence of "subinclude" triggers * preprocessing.) */ - private static Function newMockSubincludeFunction(final PackageContext context) { - return new MixedModeFunction("mocksubinclude", ImmutableList.of("label", "path"), 2, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) - throws ConversionException { - Label label = Type.LABEL.convert(args[0], "'mocksubinclude' argument", - context.pkgBuilder.getBuildFileLabel()); - String pathString = Type.STRING.convert(args[1], "'mocksubinclude' argument"); - Path path = pathString.isEmpty() - ? null - : context.pkgBuilder.getFilename().getRelative(pathString); - // A subinclude within a package counts as a file declaration. - if (label.getPackageIdentifier().equals(context.pkgBuilder.getPackageIdentifier())) { - Location location = ast.getLocation(); - if (location == null) { - location = Location.fromFile(context.pkgBuilder.getFilename()); + @SkylarkSignature(name = "mocksubinclude", returnType = Environment.NoneType.class, + doc = "implement the mocksubinclude function emitted by the PythonPreprocessor", + mandatoryPositionals = { + @Param(name = "label", type = Object.class, + doc = "a label designator."), + @Param(name = "path", type = String.class, + doc = "a path.")}, + documented = false, useLocation = true) + protected static final BuiltinFunction.Factory newMockSubincludeFunction = + new BuiltinFunction.Factory("mocksubinclude") { + public BuiltinFunction create(final PackageContext context) { + return new BuiltinFunction("mocksubinclude", this) { + public Environment.NoneType invoke(Object labelO, String pathString, + Location loc) throws ConversionException { + Label label = Type.LABEL.convert(labelO, "'mocksubinclude' argument", + context.pkgBuilder.getBuildFileLabel()); + Path path = pathString.isEmpty() + ? null : context.pkgBuilder.getFilename().getRelative(pathString); + // A subinclude within a package counts as a file declaration. + if (label.getPackageIdentifier().equals(context.pkgBuilder.getPackageIdentifier())) { + if (loc == null) { + loc = Location.fromFile(context.pkgBuilder.getFilename()); + } + context.pkgBuilder.createInputFileMaybe(label, loc); + } + + context.pkgBuilder.addSubinclude(label, path); + return Environment.NONE; } - context.pkgBuilder.createInputFileMaybe(label, location); - } - - context.pkgBuilder.addSubinclude(label, path); - return Environment.NONE; + }; } }; - } /** * Fake function: subinclude calls are ignored * They will disappear after the Python preprocessing. */ - private static Function newSubincludeFunction() { - return new MixedModeFunction("subinclude", ImmutableList.of("file"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) { - return Environment.NONE; + @SkylarkSignature(name = "subinclude", returnType = Environment.NoneType.class, + mandatoryPositionals = {@Param(name = "file", doc = "(ignored)")}, + doc = "fake function to skip over subinclude statements", + documented = false) + private static BuiltinFunction.Factory newSubincludeFunction = + new BuiltinFunction.Factory("subinclude") { + public BuiltinFunction create() { + return new BuiltinFunction("subinclude", this) { + public Environment.NoneType invoke(Object file) { + return Environment.NONE; + } + }; } }; - } /** * Returns a function value implementing "environment_group" in the specified package context. @@ -585,64 +613,90 @@ public final class PackageFactory { * <p>Where ":env1", "env2", ... are all environment rules declared in the same package. All * parameters are mandatory. */ - private static Function newEnvironmentGroupFunction(final PackageContext context) { - List<String> params = ImmutableList.of("name", "environments", "defaults"); - return new MixedModeFunction("environment_group", params, params.size(), true) { - @Override - public Object call(Object[] namedArgs, FuncallExpression ast) - throws EvalException, ConversionException { - Preconditions.checkState(namedArgs[0] != null); - String name = Type.STRING.convert(namedArgs[0], "'environment_group' argument"); - Preconditions.checkState(namedArgs[1] != null); - List<Label> environments = Type.LABEL_LIST.convert( - namedArgs[1], "'environment_group argument'", context.pkgBuilder.getBuildFileLabel()); - Preconditions.checkState(namedArgs[2] != null); - List<Label> defaults = Type.LABEL_LIST.convert( - namedArgs[2], "'environment_group argument'", context.pkgBuilder.getBuildFileLabel()); - - try { - context.pkgBuilder.addEnvironmentGroup(name, environments, defaults, - context.eventHandler, ast.getLocation()); - return Environment.NONE; - } catch (Label.SyntaxException e) { - throw new EvalException(ast.getLocation(), - "environment group has invalid name: " + name + ": " + e.getMessage()); - } catch (Package.NameConflictException e) { - throw new EvalException(ast.getLocation(), e.getMessage()); - } + @SkylarkSignature(name = "environment_group", returnType = Environment.NoneType.class, + doc = "Defines a cc_library, by wrapping around the usual library " + + "and also defining a headers target.", + mandatoryNamedOnly = { + @Param(name = "name", type = String.class, + doc = "The name of the rule."), + // Both parameter below are lists of label designators + @Param(name = "environments", type = HackHackEitherList.class, generic1 = Object.class, + doc = "A list of Labels for the environments to be grouped, from the same package."), + @Param(name = "defaults", type = HackHackEitherList.class, generic1 = Object.class, + doc = "A list of Labels.")}, // TODO(bazel-team): document what that is + documented = false, useLocation = true) + protected static final BuiltinFunction.Factory newEnvironmentGroupFunction = + new BuiltinFunction.Factory("environment_group") { + public BuiltinFunction create(final PackageContext context) { + return new BuiltinFunction("environment_group", this) { + public Environment.NoneType invoke(String name, Object environmentsO, Object defaultsO, + Location loc) throws EvalException, ConversionException { + List<Label> environments = Type.LABEL_LIST.convert(environmentsO, + "'environment_group argument'", context.pkgBuilder.getBuildFileLabel()); + List<Label> defaults = Type.LABEL_LIST.convert(defaultsO, + "'environment_group argument'", context.pkgBuilder.getBuildFileLabel()); + + try { + context.pkgBuilder.addEnvironmentGroup(name, environments, defaults, + context.eventHandler, loc); + return Environment.NONE; + } catch (Label.SyntaxException e) { + throw new EvalException(loc, + "environment group has invalid name: " + name + ": " + e.getMessage()); + } catch (Package.NameConflictException e) { + throw new EvalException(loc, e.getMessage()); + } + } + }; } }; - } /** * Returns a function-value implementing "exports_files" in the specified * package context. */ - private static Function newExportsFilesFunction() { - List<String> params = ImmutableList.of("srcs", "visibility", "licenses"); - return new MixedModeFunction("exports_files", params, 1, false) { - @Override - public Object call(Object[] namedArgs, FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - return callExportsFiles(ast, env, namedArgs); - } - }; - } + @SkylarkSignature(name = "exports_files", returnType = Environment.NoneType.class, + doc = "Declare a set of files as exported", + mandatoryPositionals = { + @Param(name = "srcs", type = HackHackEitherList.class, generic1 = String.class, + doc = "A list of strings, the names of the files to export.")}, + optionalPositionals = { + // TODO(blaze-team): make it possible to express a list of label designators, + // i.e. a java List or Skylark list of Label or String. + @Param(name = "visibility", type = HackHackEitherList.class, noneable = true, + defaultValue = "None", + doc = "A list of Labels specifying the visibility of the exported files " + + "(defaults to public)"), + @Param(name = "licenses", type = HackHackEitherList.class, generic1 = String.class, + noneable = true, defaultValue = "None", + doc = "A list of strings specifying the licenses used in the exported code.")}, + documented = false, useAst = true, useEnvironment = true) + protected static final BuiltinFunction.Factory newExportsFilesFunction = + new BuiltinFunction.Factory("exports_files") { + public BuiltinFunction create () { + return new BuiltinFunction("exports_files", this) { + public Environment.NoneType invoke(Object srcs, Object visibility, Object licenses, + FuncallExpression ast, Environment env) + throws EvalException, ConversionException { + return callExportsFiles(srcs, visibility, licenses, ast, env); + } + }; + } + }; - static Object callExportsFiles(FuncallExpression ast, Environment env, Object[] namedArgs) - throws EvalException, ConversionException { + static Environment.NoneType callExportsFiles(Object srcs, Object visibilityO, Object licensesO, + FuncallExpression ast, Environment env) throws EvalException, ConversionException { Package.LegacyBuilder pkgBuilder = getContext(env, ast).pkgBuilder; - List<String> files = Type.STRING_LIST.convert(namedArgs[0], "'exports_files' operand"); + List<String> files = Type.STRING_LIST.convert(srcs, "'exports_files' operand"); - RuleVisibility visibility = namedArgs[1] == null + RuleVisibility visibility = EvalUtils.isNullOrNone(visibilityO) ? ConstantRuleVisibility.PUBLIC : getVisibility(Type.LABEL_LIST.convert( - namedArgs[1], - "'exports_files' operand", - pkgBuilder.getBuildFileLabel())); - License license = namedArgs[2] == null - ? null - : Type.LICENSE.convert(namedArgs[2], "'exports_files' operand"); + visibilityO, + "'exports_files' operand", + pkgBuilder.getBuildFileLabel())); + // TODO(bazel-team): is licenses plural or singular? + License license = Type.LICENSE.convertOptional(licensesO, "'exports_files' operand"); for (String file : files) { String errorMessage = LabelValidator.validateTargetName(file); @@ -683,67 +737,94 @@ public final class PackageFactory { * context. * TODO(bazel-team): Remove in favor of package.licenses. */ - private static Function newLicensesFunction(final PackageContext context) { - return new MixedModeFunction("licenses", ImmutableList.of("object"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) { - try { - License license = Type.LICENSE.convert(args[0], "'licenses' operand"); - context.pkgBuilder.setDefaultLicense(license); - } catch (ConversionException e) { - context.eventHandler.handle(Event.error(ast.getLocation(), e.getMessage())); - context.pkgBuilder.setContainsErrors(); - } - return Environment.NONE; + @SkylarkSignature(name = "licenses", returnType = Environment.NoneType.class, + doc = "Declare the license(s) for the code in the current package.", + mandatoryPositionals = { + @Param(name = "license_strings", type = HackHackEitherList.class, generic1 = String.class, + doc = "A list of strings, the names of the licenses used.")}, + documented = false, useLocation = true) + protected static final BuiltinFunction.Factory newLicensesFunction = + new BuiltinFunction.Factory("licenses") { + public BuiltinFunction create(final PackageContext context) { + return new BuiltinFunction("licenses", this) { + public Environment.NoneType invoke(Object licensesO, Location loc) { + try { + License license = Type.LICENSE.convert(licensesO, "'licenses' operand"); + context.pkgBuilder.setDefaultLicense(license); + } catch (ConversionException e) { + context.eventHandler.handle(Event.error(loc, e.getMessage())); + context.pkgBuilder.setContainsErrors(); + } + return Environment.NONE; + } + }; } }; - } /** * Returns a function-value implementing "distribs" in the specified package * context. * TODO(bazel-team): Remove in favor of package.distribs. */ - private static Function newDistribsFunction(final PackageContext context) { - return new MixedModeFunction("distribs", ImmutableList.of("object"), 1, false) { - @Override - public Object call(Object[] args, FuncallExpression ast) { - try { - Set<DistributionType> distribs = Type.DISTRIBUTIONS.convert(args[0], - "'distribs' operand"); - context.pkgBuilder.setDefaultDistribs(distribs); - } catch (ConversionException e) { - context.eventHandler.handle(Event.error(ast.getLocation(), e.getMessage())); - context.pkgBuilder.setContainsErrors(); - } - return Environment.NONE; + @SkylarkSignature(name = "distribs", returnType = Environment.NoneType.class, + doc = "Declare the distribution(s) for the code in the current package.", + mandatoryPositionals = { + @Param(name = "distribution_strings", type = Object.class, + doc = "The distributions.")}, + documented = false, useLocation = true) + protected static final BuiltinFunction.Factory newDistribsFunction = + new BuiltinFunction.Factory("distribs") { + public BuiltinFunction create(final PackageContext context) { + return new BuiltinFunction("distribs", this) { + public Environment.NoneType invoke(Object object, Location loc) { + try { + Set<DistributionType> distribs = Type.DISTRIBUTIONS.convert(object, + "'distribs' operand"); + context.pkgBuilder.setDefaultDistribs(distribs); + } catch (ConversionException e) { + context.eventHandler.handle(Event.error(loc, e.getMessage())); + context.pkgBuilder.setContainsErrors(); + } + return Environment.NONE; + } + }; } }; - } - private static Function newPackageGroupFunction() { - List<String> params = ImmutableList.of("name", "packages", "includes"); - return new MixedModeFunction("package_group", params, 1, true) { - @Override - public Object call(Object[] namedArgs, FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - return callPackageFunction(ast, env, namedArgs); + @SkylarkSignature(name = "package_group", returnType = Environment.NoneType.class, + doc = "Declare a set of files as exported", + mandatoryNamedOnly = { + @Param(name = "name", type = String.class, + doc = "The name of the rule.")}, + optionalNamedOnly = { + @Param(name = "packages", type = HackHackEitherList.class, generic1 = String.class, + defaultValue = "[]", + doc = "A list of Strings specifying the packages grouped."), + // java list or list of label designators: Label or String + @Param(name = "includes", type = HackHackEitherList.class, generic1 = Object.class, + defaultValue = "[]", + doc = "A list of Label specifiers for the files to include.")}, + documented = false, useAst = true, useEnvironment = true) + protected static final BuiltinFunction.Factory newPackageGroupFunction = + new BuiltinFunction.Factory("package_group") { + public BuiltinFunction create() { + return new BuiltinFunction("package_group", this) { + public Environment.NoneType invoke(String name, Object packages, Object includes, + FuncallExpression ast, Environment env) throws EvalException, ConversionException { + return callPackageFunction(name, packages, includes, ast, env); + } + }; } }; - } - static Object callPackageFunction(FuncallExpression ast, Environment env, Object[] namedArgs) - throws EvalException, ConversionException { + static Environment.NoneType callPackageFunction(String name, Object packagesO, Object includesO, + FuncallExpression ast, Environment env) throws EvalException, ConversionException { PackageContext context = getContext(env, ast); - Preconditions.checkState(namedArgs[0] != null); - String name = Type.STRING.convert(namedArgs[0], "'package_group' argument"); - List<String> packages = namedArgs[1] == null - ? Collections.<String>emptyList() - : Type.STRING_LIST.convert(namedArgs[1], "'package_group' argument"); - List<Label> includes = namedArgs[2] == null - ? Collections.<Label>emptyList() - : Type.LABEL_LIST.convert(namedArgs[2], "'package_group argument'", - context.pkgBuilder.getBuildFileLabel()); + + List<String> packages = Type.STRING_LIST.convert( + packagesO, "'package_group.packages argument'"); + List<Label> includes = Type.LABEL_LIST.convert(includesO, + "'package_group.includes argument'", context.pkgBuilder.getBuildFileLabel()); try { context.pkgBuilder.addPackageGroup(name, packages, includes, context.eventHandler, @@ -774,10 +855,23 @@ public final class PackageFactory { * context. */ private static Function newPackageFunction( - final Map<String, PackageArgument<?>> packageArguments) { - return new MixedModeFunction("package", packageArguments.keySet(), 0, true) { + final ImmutableMap<String, PackageArgument<?>> packageArguments) { + // Flatten the map of argument name of PackageArgument specifier in two co-indexed arrays: + // one for the argument names, to create a FunctionSignature when we create the function, + // one of the PackageArgument specifiers, over which to iterate at every function invocation + // at the same time that we iterate over the function arguments. + final int numArgs = packageArguments.size(); + final String[] argumentNames = new String[numArgs]; + final PackageArgument<?>[] argumentSpecifiers = new PackageArgument<?>[numArgs]; + int i = 0; + for (Map.Entry<String, PackageArgument<?>> entry : packageArguments.entrySet()) { + argumentNames[i] = entry.getKey(); + argumentSpecifiers[i++] = entry.getValue(); + } + + return new BaseFunction("package", FunctionSignature.namedOnly(0, argumentNames)) { @Override - public Object call(Object[] namedArguments, FuncallExpression ast, Environment env) + public Object call(Object[] arguments, FuncallExpression ast, Environment env) throws EvalException, ConversionException { Package.LegacyBuilder pkgBuilder = getContext(env, ast).pkgBuilder; @@ -792,16 +886,12 @@ public final class PackageFactory { // Parse params boolean foundParameter = false; - int argNumber = 0; - for (Map.Entry<String, PackageArgument<?>> entry : packageArguments.entrySet()) { - Object arg = namedArguments[argNumber]; - argNumber += 1; - if (arg == null) { - continue; + for (int i = 0; i < numArgs; i++) { + Object value = arguments[i]; + if (value != null) { + foundParameter = true; + argumentSpecifiers[i].convertAndProcess(pkgBuilder, ast.getLocation(), value); } - - foundParameter = true; - entry.getValue().convertAndProcess(pkgBuilder, ast.getLocation(), arg); } if (!foundParameter) { @@ -852,18 +942,13 @@ public final class PackageFactory { * Returns a function-value implementing the build rule "ruleClass" (e.g. cc_library) in the * specified package context. */ - private static Function newRuleFunction(final RuleFactory ruleFactory, - final String ruleClass) { - return new AbstractFunction(ruleClass) { - @Override - public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast, - Environment env) + private static BuiltinFunction newRuleFunction( + final RuleFactory ruleFactory, final String ruleClass) { + return new BuiltinFunction(ruleClass, FunctionSignature.KWARGS, BuiltinFunction.USE_AST_ENV) { + @SuppressWarnings("unchecked") + public Environment.NoneType invoke(Map<String, Object> kwargs, + FuncallExpression ast, Environment env) throws EvalException { - if (!args.isEmpty()) { - throw new EvalException(ast.getLocation(), - "build rules do not accept positional parameters"); - } - try { addRule(ruleFactory, ruleClass, getContext(env, ast), kwargs, ast); } catch (RuleFactory.InvalidRuleException | Package.NameConflictException e) { @@ -1117,15 +1202,15 @@ public final class PackageFactory { private void buildPkgEnv(Environment pkgEnv, String packageName, PackageContext context, RuleFactory ruleFactory) { - pkgEnv.update("distribs", newDistribsFunction(context)); - pkgEnv.update("glob", newGlobFunction(context, /*async=*/false)); - pkgEnv.update("mocksubinclude", newMockSubincludeFunction(context)); - pkgEnv.update("licenses", newLicensesFunction(context)); - pkgEnv.update("exports_files", newExportsFilesFunction()); - pkgEnv.update("package_group", newPackageGroupFunction()); + pkgEnv.update("distribs", newDistribsFunction.apply(context)); + pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/false)); + pkgEnv.update("mocksubinclude", newMockSubincludeFunction.apply(context)); + pkgEnv.update("licenses", newLicensesFunction.apply(context)); + pkgEnv.update("exports_files", newExportsFilesFunction.apply()); + pkgEnv.update("package_group", newPackageGroupFunction.apply()); pkgEnv.update("package", newPackageFunction(packageArguments)); - pkgEnv.update("subinclude", newSubincludeFunction()); - pkgEnv.update("environment_group", newEnvironmentGroupFunction(context)); + pkgEnv.update("subinclude", newSubincludeFunction.apply()); + pkgEnv.update("environment_group", newEnvironmentGroupFunction.apply(context)); pkgEnv.update("PACKAGE_NAME", packageName); @@ -1251,7 +1336,7 @@ public final class PackageFactory { // Stuff that closes over the package context: PackageContext context = new PackageContext(pkgBuilder, globber, NullEventHandler.INSTANCE); buildPkgEnv(pkgEnv, packageId.toString(), context, ruleFactory); - pkgEnv.update("glob", newGlobFunction(context, /*async=*/true)); + pkgEnv.update("glob", newGlobFunction.apply(context, /*async=*/true)); // The Fileset function is heavyweight in that it can run glob(). Avoid this during the // preloading phase. pkgEnv.remove("FilesetEntry"); @@ -1301,4 +1386,8 @@ public final class PackageFactory { } return true; } + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(PackageFactory.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java index b4273c5d4e..c604fff3e2 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java +++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java @@ -31,11 +31,11 @@ import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.syntax.Argument; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.FuncallExpression; +import com.google.devtools.build.lib.syntax.Function; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.Label.SyntaxException; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; -import com.google.devtools.build.lib.syntax.UserDefinedFunction; import com.google.devtools.build.lib.util.StringUtil; import com.google.devtools.build.lib.vfs.PathFragment; @@ -427,7 +427,7 @@ public final class RuleClass { PredicatesWithMessage.<Rule>alwaysTrue(); private Predicate<String> preferredDependencyPredicate = Predicates.alwaysFalse(); private List<Class<?>> advertisedProviders = new ArrayList<>(); - private UserDefinedFunction configuredTargetFunction = null; + private Function configuredTargetFunction = null; private SkylarkEnvironment ruleDefinitionEnvironment = null; private Set<Class<?>> configurationFragments = new LinkedHashSet<>(); private boolean failIfMissingConfigurationFragment; @@ -681,7 +681,7 @@ public final class RuleClass { /** * Sets the rule implementation function. Meant for Skylark usage. */ - public Builder setConfiguredTargetFunction(UserDefinedFunction func) { + public Builder setConfiguredTargetFunction(Function func) { this.configuredTargetFunction = func; return this; } @@ -834,7 +834,7 @@ public final class RuleClass { /** * The Skylark rule implementation of this RuleClass. Null for non Skylark executable RuleClasses. */ - @Nullable private final UserDefinedFunction configuredTargetFunction; + @Nullable private final Function configuredTargetFunction; /** * The Skylark rule definition environment of this RuleClass. @@ -895,7 +895,7 @@ public final class RuleClass { ConfiguredTargetFactory<?, ?> configuredTargetFactory, PredicateWithMessage<Rule> validityPredicate, Predicate<String> preferredDependencyPredicate, ImmutableSet<Class<?>> advertisedProviders, - @Nullable UserDefinedFunction configuredTargetFunction, + @Nullable Function configuredTargetFunction, @Nullable SkylarkEnvironment ruleDefinitionEnvironment, Set<Class<?>> allowedConfigurationFragments, boolean failIfMissingConfigurationFragment, boolean supportsConstraintChecking, @@ -1503,7 +1503,7 @@ public final class RuleClass { /** * Returns this RuleClass's custom Skylark rule implementation. */ - @Nullable public UserDefinedFunction getConfiguredTargetFunction() { + @Nullable public Function getConfiguredTargetFunction() { return configuredTargetFunction; } diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java index a5b438c3f2..1965b9d91c 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java +++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java @@ -15,17 +15,16 @@ package com.google.devtools.build.lib.packages; import com.google.devtools.build.lib.packages.Type.ConversionException; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.GlobList; -import com.google.devtools.build.lib.syntax.SkylarkFunction; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; - -import java.util.Map; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; /** * A class for the Skylark native module. @@ -37,6 +36,7 @@ import java.util.Map; + "Extra helper functions:") public class SkylarkNativeModule { + // TODO(bazel-team): shouldn't we return a SkylarkList instead? @SkylarkSignature(name = "glob", objectType = SkylarkNativeModule.class, returnType = GlobList.class, doc = "Glob returns a list of every file in the current package that:<ul>\n" @@ -55,17 +55,15 @@ public class SkylarkNativeModule { @Param(name = "exclude_directories", type = Integer.class, defaultValue = "1", doc = "A flag whether to exclude directories or not.")}, useAst = true, useEnvironment = true) - private static final SkylarkFunction glob = new SkylarkFunction("glob") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) + private static final BuiltinFunction glob = new BuiltinFunction("glob") { + public GlobList<String> invoke( + SkylarkList includes, SkylarkList excludes, Integer excludeDirectories, + FuncallExpression ast, Environment env) throws EvalException, ConversionException, InterruptedException { - return PackageFactory.callGlob(null, false, ast, env, new Object[] { - kwargs.get("includes"), - kwargs.get("excludes"), - kwargs.get("exclude_directories") - }); - } - }; + return PackageFactory.callGlob( + null, false, includes, excludes, excludeDirectories != 0, ast, env); + } + }; @SkylarkSignature(name = "package_group", objectType = SkylarkNativeModule.class, returnType = Environment.NoneType.class, @@ -82,17 +80,12 @@ public class SkylarkNativeModule { defaultValue = "[]", doc = "Other package groups that are included in this one.")}, useAst = true, useEnvironment = true) - private static final SkylarkFunction packageGroup = new SkylarkFunction("package_group") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - return PackageFactory.callPackageFunction(ast, env, new Object[] { - kwargs.get("name"), - kwargs.get("packages"), - kwargs.get("includes") - }); - } - }; + private static final BuiltinFunction packageGroup = new BuiltinFunction("package_group") { + public Environment.NoneType invoke(String name, SkylarkList packages, SkylarkList includes, + FuncallExpression ast, Environment env) throws EvalException, ConversionException { + return PackageFactory.callPackageFunction(name, packages, includes, ast, env); + } + }; @SkylarkSignature(name = "exports_files", objectType = SkylarkNativeModule.class, returnType = Environment.NoneType.class, @@ -111,17 +104,17 @@ public class SkylarkNativeModule { @Param(name = "licenses", type = SkylarkList.class, generic1 = String.class, noneable = true, doc = "Licenses to be specified.")}, useAst = true, useEnvironment = true) - private static final SkylarkFunction exportsFiles = new SkylarkFunction("exports_files") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException, ConversionException { - return PackageFactory.callExportsFiles(ast, env, new Object[] { - kwargs.get("srcs"), - kwargs.get("visibility"), - kwargs.get("licenses") - }); - } - }; + private static final BuiltinFunction exportsFiles = new BuiltinFunction("exports_files") { + public Environment.NoneType invoke(SkylarkList srcs, Object visibility, Object licenses, + FuncallExpression ast, Environment env) + throws EvalException, ConversionException { + return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env); + } + }; public static final SkylarkNativeModule NATIVE_MODULE = new SkylarkNativeModule(); + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkNativeModule.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java index 7d39e0dcfa..46d31e225d 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkAttr.java @@ -21,6 +21,7 @@ import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; import com.google.devtools.build.lib.packages.Attribute.SkylarkLateBound; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.packages.Type.ConversionException; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; @@ -28,11 +29,11 @@ import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; -import com.google.devtools.build.lib.syntax.SkylarkFunction; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; import com.google.devtools.build.lib.syntax.UserDefinedFunction; import com.google.devtools.build.lib.util.FileTypeSet; @@ -171,11 +172,14 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction integer = new SkylarkFunction("int") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.INTEGER, ast, env); + private static BuiltinFunction integer = new BuiltinFunction("int") { + public Attribute.Builder<?> invoke(Integer defaultInt, + SkylarkList flags, Boolean mandatory, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultInt, "flags", flags, "mandatory", mandatory, "cfg", cfg), + Type.INTEGER, ast, env); } }; @@ -193,11 +197,14 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction string = new SkylarkFunction("string") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.STRING, ast, env); + private static BuiltinFunction string = new BuiltinFunction("string") { + public Attribute.Builder<?> invoke(String defaultString, + SkylarkList flags, Boolean mandatory, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultString, "flags", flags, "mandatory", mandatory, "cfg", cfg), + Type.STRING, ast, env); } }; @@ -230,18 +237,31 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction label = new SkylarkFunction("label") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.LABEL, ast, env); + private static BuiltinFunction label = new BuiltinFunction("label") { + public Attribute.Builder<?> invoke( + Object defaultO, + Boolean executable, + SkylarkList flags, + Object allowFiles, + Boolean mandatory, + SkylarkList providers, + Object allowRules, + Boolean singleFile, + Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultO, "executable", executable, "flags", flags, + "allow_files", allowFiles, "mandatory", mandatory, "providers", providers, + "allow_rules", allowRules, "single_file", singleFile, "cfg", cfg), + Type.LABEL, ast, env); } }; @SkylarkSignature(name = "string_list", doc = "Creates an attribute of type list of strings", objectType = SkylarkAttr.class, - returnType = Attribute.class, + returnType = Attribute.Builder.class, optionalPositionals = { @Param(name = "default", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]", @@ -255,11 +275,19 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction stringList = new SkylarkFunction("string_list") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.STRING_LIST, ast, env); + private static BuiltinFunction stringList = new BuiltinFunction("string_list") { + public Attribute.Builder<?> invoke( + SkylarkList defaultList, + SkylarkList flags, + Boolean mandatory, + Boolean nonEmpty, + Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultList, + "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg), + Type.STRING_LIST, ast, env); } }; @@ -288,11 +316,22 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction labelList = new SkylarkFunction("label_list") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.LABEL_LIST, ast, env); + private static BuiltinFunction labelList = new BuiltinFunction("label_list") { + public Attribute.Builder<?> invoke( + Object defaultList, + Object allowFiles, + Object allowRules, + SkylarkList providers, + SkylarkList flags, + Boolean mandatory, + Boolean nonEmpty, + Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap("default", defaultList, + "allow_files", allowFiles, "allow_rules", allowRules, "providers", providers, + "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg), + Type.LABEL_LIST, ast, env); } }; @@ -310,11 +349,14 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction bool = new SkylarkFunction("bool") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.BOOLEAN, ast, env); + private static BuiltinFunction bool = new BuiltinFunction("bool") { + public Attribute.Builder<?> invoke(Boolean defaultBool, + SkylarkList flags, Boolean mandatory, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultBool, "flags", flags, "mandatory", mandatory, "cfg", cfg), + Type.BOOLEAN, ast, env); } }; @@ -334,11 +376,14 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction output = new SkylarkFunction("output") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.OUTPUT, ast, env); + private static BuiltinFunction output = new BuiltinFunction("output") { + public Attribute.Builder<?> invoke(Object defaultO, + SkylarkList flags, Boolean mandatory, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultO, "flags", flags, "mandatory", mandatory, "cfg", cfg), + Type.OUTPUT, ast, env); } }; @@ -359,11 +404,14 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction outputList = new SkylarkFunction("output_list") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.OUTPUT_LIST, ast, env); + private static BuiltinFunction outputList = new BuiltinFunction("output_list") { + public Attribute.Builder<?> invoke(SkylarkList defaultList, + SkylarkList flags, Boolean mandatory, Boolean nonEmpty, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap("default", defaultList, + "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg), + Type.OUTPUT_LIST, ast, env); } }; @@ -384,11 +432,14 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction stringDict = new SkylarkFunction("string_dict") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.STRING_DICT, ast, env); + private static BuiltinFunction stringDict = new BuiltinFunction("string_dict") { + public Attribute.Builder<?> invoke(Map<?, ?> defaultO, + SkylarkList flags, Boolean mandatory, Boolean nonEmpty, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap("default", defaultO, + "flags", flags, "mandatory", mandatory, "non_empty", nonEmpty, "cfg", cfg), + Type.STRING_DICT, ast, env); } }; @@ -408,11 +459,18 @@ public final class SkylarkAttr { @Param(name = "cfg", type = ConfigurationTransition.class, noneable = true, defaultValue = "None", doc = CONFIGURATION_DOC)}, useAst = true, useEnvironment = true) - private static SkylarkFunction license = new SkylarkFunction("license") { - @Override - public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) - throws EvalException { - return createAttribute(kwargs, Type.LICENSE, ast, env); + private static BuiltinFunction license = new BuiltinFunction("license") { + public Attribute.Builder<?> invoke(Object defaultO, + SkylarkList flags, Boolean mandatory, Object cfg, + FuncallExpression ast, Environment env) throws EvalException { + return createAttribute( + EvalUtils.optionMap( + "default", defaultO, "flags", flags, "mandatory", mandatory, "cfg", cfg), + Type.LICENSE, ast, env); } }; + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkAttr.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java index 260c7e4a8f..a26248656f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java @@ -18,16 +18,13 @@ import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.collect.nestedset.NestedSet; -import com.google.devtools.build.lib.events.Location; -import com.google.devtools.build.lib.syntax.EvalException; -import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkNestedSet; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; - -import java.util.Map; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; /** * A Skylark module class to create memory efficient command lines. @@ -36,23 +33,17 @@ import java.util.Map; doc = "Module for creating memory efficient command lines.") public class SkylarkCommandLine { - @SkylarkSignature(name = "join_paths", + @SkylarkSignature(name = "join_paths", objectType = SkylarkCommandLine.class, + returnType = String.class, doc = "Creates a single command line argument joining the paths of a set " + "of files on the separator string.", - objectType = SkylarkCommandLine.class, - returnType = String.class, mandatoryPositionals = { @Param(name = "separator", type = String.class, doc = "the separator string to join on"), @Param(name = "files", type = SkylarkNestedSet.class, generic1 = Artifact.class, doc = "the files to concatenate")}) - private static SimpleSkylarkFunction joinPaths = - new SimpleSkylarkFunction("join_paths") { - @Override - public Object call(Map<String, Object> params, Location loc) - throws EvalException { - final String separator = (String) params.get("separator"); - final NestedSet<Artifact> artifacts = - ((SkylarkNestedSet) params.get("files")).getSet(Artifact.class); + private static BuiltinFunction joinPaths = new BuiltinFunction("join_paths") { + public String invoke(String separator, SkylarkNestedSet files) { + NestedSet<Artifact> artifacts = files.getSet(Artifact.class); // TODO(bazel-team): lazy evaluate return Artifact.joinExecPaths(separator, artifacts); } @@ -70,12 +61,8 @@ public class SkylarkCommandLine { doc = "The template to use for the transformation, <code>%{path}</code> and " + "<code>%{short_path}</code> being substituted with the corresponding fields of each" + " file.")}) - private static SimpleSkylarkFunction template = new SimpleSkylarkFunction("template") { - @Override - public Object call(Map<String, Object> params, Location loc) - throws EvalException { - final String template = (String) params.get("template"); - SkylarkNestedSet items = (SkylarkNestedSet) params.get("items"); + private static BuiltinFunction template = new BuiltinFunction("template") { + public SkylarkList invoke(final SkylarkNestedSet items, final String template) { return SkylarkList.lazyList(Iterables.transform(items, new Function<Object, String>() { @Override public String apply(Object input) { @@ -87,4 +74,8 @@ public class SkylarkCommandLine { }), String.class); } }; + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkCommandLine.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java index 226ceebee3..fdb69a3250 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java @@ -21,11 +21,10 @@ import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.packages.MethodLibrary; import com.google.devtools.build.lib.packages.SkylarkNativeModule; +import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvaluationContext; -import com.google.devtools.build.lib.syntax.Function; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; -import com.google.devtools.build.lib.syntax.SkylarkFunction; import com.google.devtools.build.lib.syntax.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.ValidationEnvironment; @@ -43,9 +42,11 @@ import java.util.Set; public class SkylarkModules { /** - * The list of built in Skylark modules. Documentation is generated automatically for all these - * modules. They are also registered with the {@link ValidationEnvironment} and the - * {@link SkylarkEnvironment}. Note that only {@link SkylarkFunction}s are handled properly. + * The list of built in Skylark modules. + * Documentation is generated automatically for all these modules. + * They are also registered with the {@link ValidationEnvironment} + * and the {@link SkylarkEnvironment}. + * Note that only functions with a {@link SkylarkSignature} annotations are handled properly. */ // TODO(bazel-team): find a more general, more automated way of registering classes and building // initial environments. And don't give syntax.Environment and packages.MethodLibrary a special @@ -57,19 +58,20 @@ public class SkylarkModules { SkylarkRuleClassFunctions.class, SkylarkRuleImplementationFunctions.class); - private static final ImmutableMap<Class<?>, ImmutableList<Function>> FUNCTION_MAP; + private static final ImmutableMap<Class<?>, ImmutableList<BaseFunction>> FUNCTION_MAP; private static final ImmutableMap<String, Object> OBJECTS; static { try { - ImmutableMap.Builder<Class<?>, ImmutableList<Function>> functionMap = ImmutableMap.builder(); + ImmutableMap.Builder<Class<?>, ImmutableList<BaseFunction>> functionMap = + ImmutableMap.builder(); ImmutableMap.Builder<String, Object> objects = ImmutableMap.builder(); for (Class<?> moduleClass : MODULES) { if (moduleClass.isAnnotationPresent(SkylarkModule.class)) { objects.put(moduleClass.getAnnotation(SkylarkModule.class).name(), moduleClass.newInstance()); } - ImmutableList.Builder<Function> functions = ImmutableList.builder(); + ImmutableList.Builder<BaseFunction> functions = ImmutableList.builder(); collectSkylarkFunctionsAndObjectsFromFields(moduleClass, functions, objects); functionMap.put(moduleClass, functions.build()); } @@ -97,8 +99,8 @@ public class SkylarkModules { private static void setupEnvironment(Environment env) { MethodLibrary.setupMethodEnvironment(env); - for (Map.Entry<Class<?>, ImmutableList<Function>> entry : FUNCTION_MAP.entrySet()) { - for (Function function : entry.getValue()) { + for (Map.Entry<Class<?>, ImmutableList<BaseFunction>> entry : FUNCTION_MAP.entrySet()) { + for (BaseFunction function : entry.getValue()) { if (function.getObjectType() != null) { env.registerFunction(function.getObjectType(), function.getName(), function); } else { @@ -143,11 +145,11 @@ public class SkylarkModules { } /** - * Collects the Functions from the fields of the class of the object parameter + * Collects the BaseFunctions from the fields of the class of the object parameter * and adds them into the builder. */ private static void collectSkylarkFunctionsAndObjectsFromFields(Class<?> type, - ImmutableList.Builder<Function> functions, ImmutableMap.Builder<String, Object> objects) { + ImmutableList.Builder<BaseFunction> functions, ImmutableMap.Builder<String, Object> objects) { try { for (Field field : type.getDeclaredFields()) { if (field.isAnnotationPresent(SkylarkSignature.class)) { @@ -156,12 +158,8 @@ public class SkylarkModules { field.setAccessible(true); SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class); Object value = field.get(null); - if (SkylarkFunction.class.isAssignableFrom(field.getType())) { - SkylarkFunction function = (SkylarkFunction) value; - if (!function.isConfigured()) { - function.configure(annotation); - } - functions.add(function); + if (BaseFunction.class.isAssignableFrom(field.getType())) { + functions.add((BaseFunction) value); } else { objects.put(annotation.name(), value); } @@ -174,14 +172,14 @@ public class SkylarkModules { } /** - * Collects the Functions from the fields of the class of the object parameter + * Collects the BaseFunctions from the fields of the class of the object parameter * and adds their class and their corresponding return value to the builder. */ private static void collectSkylarkTypesFromFields(Class<?> classObject, Set<String> builtIn) { for (Field field : classObject.getDeclaredFields()) { if (field.isAnnotationPresent(SkylarkSignature.class)) { SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class); - if (SkylarkFunction.class.isAssignableFrom(field.getType())) { + if (BaseFunction.class.isAssignableFrom(field.getType())) { // Ignore non-global values. if (annotation.objectType().equals(Object.class)) { builtIn.add(annotation.name()); diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java index 3cb4e28f96..14bdcd7bbc 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java @@ -54,8 +54,8 @@ import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.packages.TestSize; import com.google.devtools.build.lib.packages.Type; import com.google.devtools.build.lib.packages.Type.ConversionException; -import com.google.devtools.build.lib.syntax.AbstractFunction; import com.google.devtools.build.lib.syntax.BaseFunction; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; import com.google.devtools.build.lib.syntax.Environment; @@ -64,17 +64,15 @@ import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Function; +import com.google.devtools.build.lib.syntax.FunctionSignature; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; -import com.google.devtools.build.lib.syntax.SkylarkFunction; -import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; -import com.google.devtools.build.lib.syntax.UserDefinedFunction; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; -import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -182,7 +180,7 @@ public class SkylarkRuleClassFunctions { "Creates a new rule. Store it in a global value, so that it can be loaded and called " + "from BUILD files.", onlyLoadingPhase = true, - returnType = BaseFunction.class, + returnType = Function.class, mandatoryPositionals = { @Param(name = "implementation", type = Function.class, doc = "the function implementing this rule, has to have exactly one parameter: " @@ -214,85 +212,85 @@ public class SkylarkRuleClassFunctions { doc = "If true, the files will be generated in the genfiles directory instead of the " + "bin directory. This is used for compatibility with existing rules.")}, useAst = true, useEnvironment = true) - private static final SkylarkFunction rule = new SkylarkFunction("rule") { - - @Override - @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces - // an Attribute.Builder instead of a Attribute.Builder<?> but it's OK. - public Object call(Map<String, Object> arguments, FuncallExpression ast, - Environment funcallEnv) throws EvalException, ConversionException { - final Location loc = ast.getLocation(); - - RuleClassType type = RuleClassType.NORMAL; - if (arguments.containsKey("test") && EvalUtils.toBoolean(arguments.get("test"))) { - type = RuleClassType.TEST; - } - - // We'll set the name later, pass the empty string for now. - final RuleClass.Builder builder = type == RuleClassType.TEST - ? new RuleClass.Builder("", type, true, testBaseRule) - : new RuleClass.Builder("", type, true, baseRule); - - for (Map.Entry<String, Attribute.Builder> attr : castMap(arguments.get("attrs"), - String.class, Attribute.Builder.class, "attrs").entrySet()) { - Attribute.Builder<?> attrBuilder = attr.getValue(); - String attrName = attributeToNative(attr.getKey(), loc, + private static final BuiltinFunction rule = new BuiltinFunction("rule") { + @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces + // an Attribute.Builder instead of a Attribute.Builder<?> but it's OK. + public Function invoke(Function implementation, Boolean test, + Object attrs, Object implicitOutputs, Boolean executable, Boolean outputToGenfiles, + FuncallExpression ast, Environment funcallEnv) + throws EvalException, ConversionException { + + RuleClassType type = test ? RuleClassType.TEST : RuleClassType.NORMAL; + + // We'll set the name later, pass the empty string for now. + RuleClass.Builder builder = test + ? new RuleClass.Builder("", type, true, testBaseRule) + : new RuleClass.Builder("", type, true, baseRule); + + if (attrs != Environment.NONE) { + for (Map.Entry<String, Attribute.Builder> attr : castMap( + attrs, String.class, Attribute.Builder.class, "attrs").entrySet()) { + Attribute.Builder<?> attrBuilder = (Attribute.Builder<?>) attr.getValue(); + String attrName = attributeToNative(attr.getKey(), ast.getLocation(), attrBuilder.hasLateBoundValue()); builder.addOrOverrideAttribute(attrBuilder.build(attrName)); } - if (arguments.containsKey("executable") && (Boolean) arguments.get("executable")) { - builder.addOrOverrideAttribute( - attr("$is_executable", BOOLEAN).value(true) - .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target") - .build()); - builder.setOutputsDefaultExecutable(); - } + } + if (executable) { + builder.addOrOverrideAttribute( + attr("$is_executable", BOOLEAN).value(true) + .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target") + .build()); + builder.setOutputsDefaultExecutable(); + } - if (arguments.containsKey("outputs")) { - final Object implicitOutputs = arguments.get("outputs"); - if (implicitOutputs instanceof UserDefinedFunction) { - UserDefinedFunction func = (UserDefinedFunction) implicitOutputs; - final SkylarkCallbackFunction callback = - new SkylarkCallbackFunction(func, ast, (SkylarkEnvironment) funcallEnv); - builder.setImplicitOutputsFunction( - new SkylarkImplicitOutputsFunctionWithCallback(callback, loc)); - } else { - builder.setImplicitOutputsFunction(new SkylarkImplicitOutputsFunctionWithMap( - ImmutableMap.copyOf(castMap(arguments.get("outputs"), String.class, String.class, - "implicit outputs of the rule class")))); + if (!(funcallEnv instanceof SkylarkEnvironment)) { + System.out.println("rule called from non-Skylark environment!"); + // throw new EvaluationException("rule not accessible at the toplevel"); + } + + if (implicitOutputs != Environment.NONE) { + if (implicitOutputs instanceof Function) { + Function func = (Function) implicitOutputs; + final SkylarkCallbackFunction callback = + new SkylarkCallbackFunction(func, ast, (SkylarkEnvironment) funcallEnv); + builder.setImplicitOutputsFunction( + new SkylarkImplicitOutputsFunctionWithCallback(callback, ast.getLocation())); + } else { + builder.setImplicitOutputsFunction(new SkylarkImplicitOutputsFunctionWithMap( + ImmutableMap.copyOf(castMap(implicitOutputs, String.class, String.class, + "implicit outputs of the rule class")))); } - } + } - if (arguments.containsKey("output_to_genfiles") - && (Boolean) arguments.get("output_to_genfiles")) { - builder.setOutputToGenfiles(); - } + if (outputToGenfiles) { + builder.setOutputToGenfiles(); + } - builder.setConfiguredTargetFunction( - (UserDefinedFunction) arguments.get("implementation")); - builder.setRuleDefinitionEnvironment((SkylarkEnvironment) funcallEnv); - return new RuleFunction(builder, type); + builder.setConfiguredTargetFunction(implementation); + builder.setRuleDefinitionEnvironment((SkylarkEnvironment) funcallEnv); + return new RuleFunction(builder, type); } }; // This class is needed for testing - static final class RuleFunction extends AbstractFunction { + static final class RuleFunction extends BaseFunction { // Note that this means that we can reuse the same builder. // This is fine since we don't modify the builder from here. private final RuleClass.Builder builder; private final RuleClassType type; public RuleFunction(Builder builder, RuleClassType type) { - super("rule"); + super("rule", FunctionSignature.KWARGS); this.builder = builder; this.type = type; } + @Override @SuppressWarnings("unchecked") // the magic hidden $pkg_context variable is guaranteed // to be a PackageContext - @Override - public Object call(List<Object> args, Map<String, Object> kwargs, FuncallExpression ast, - Environment env) throws EvalException, InterruptedException { + public Object call(Object[] args, FuncallExpression ast, Environment env) + throws EvalException, InterruptedException, ConversionException { try { String ruleClassName = ast.getFunction().getName(); if (ruleClassName.startsWith("_")) { @@ -305,7 +303,8 @@ public class SkylarkRuleClassFunctions { } RuleClass ruleClass = builder.build(ruleClassName); PackageContext pkgContext = (PackageContext) env.lookup(PackageFactory.PKG_CONTEXT); - return RuleFactory.createAndAddRule(pkgContext, ruleClass, kwargs, ast); + return RuleFactory.createAndAddRule( + pkgContext, ruleClass, (Map<String, Object>) args[0], ast); } catch (InvalidRuleException | NameConflictException | NoSuchVariableException e) { throw new EvalException(ast.getLocation(), e.getMessage()); } @@ -324,33 +323,29 @@ public class SkylarkRuleClassFunctions { mandatoryPositionals = {@Param(name = "label_string", type = String.class, doc = "the label string")}, useLocation = true) - private static final SkylarkFunction label = new SimpleSkylarkFunction("Label") { - @Override - public Object call(Map<String, Object> arguments, Location loc) throws EvalException, - ConversionException { - String labelString = (String) arguments.get("label_string"); + private static final BuiltinFunction label = new BuiltinFunction("Label") { + public Label invoke(String labelString, + Location loc) throws EvalException, ConversionException { try { return labelCache.get(labelString); } catch (ExecutionException e) { throw new EvalException(loc, "Illegal absolute label syntax: " + labelString); } - } - }; + } + }; @SkylarkSignature(name = "FileType", doc = "Creates a file filter from a list of strings. For example, to match files ending " + "with .cc or .cpp, use: <pre class=language-python>FileType([\".cc\", \".cpp\"])</pre>", returnType = SkylarkFileType.class, mandatoryPositionals = { - @Param(name = "types", type = SkylarkList.class, generic1 = String.class, + @Param(name = "types", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]", doc = "a list of the accepted file extensions")}) - private static final SkylarkFunction fileType = new SimpleSkylarkFunction("FileType") { - @Override - public Object call(Map<String, Object> arguments, Location loc) throws EvalException, - ConversionException { - return SkylarkFileType.of(castList(arguments.get("types"), String.class)); - } - }; + private static final BuiltinFunction fileType = new BuiltinFunction("FileType") { + public SkylarkFileType invoke(SkylarkList types) throws ConversionException { + return SkylarkFileType.of(castList(types, String.class)); + } + }; @SkylarkSignature(name = "to_proto", doc = "Creates a text message from the struct parameter. This method only works if all " @@ -373,68 +368,69 @@ public class SkylarkRuleClassFunctions { @Param(name = "self", type = SkylarkClassObject.class, doc = "this struct")}, useLocation = true) - private static final SkylarkFunction toProto = new SimpleSkylarkFunction("to_proto") { - @Override - public Object call(Map<String, Object> arguments, Location loc) throws EvalException, - ConversionException { - ClassObject self = (ClassObject) arguments.get("self"); - StringBuilder sb = new StringBuilder(); - printTextMessage(self, sb, 0, loc); - return sb.toString(); - } - - private void printTextMessage(ClassObject object, StringBuilder sb, - int indent, Location loc) throws EvalException { - for (String key : object.getKeys()) { - printTextMessage(key, object.getValue(key), sb, indent, loc); + private static final BuiltinFunction toProto = new BuiltinFunction("to_proto") { + public String invoke(SkylarkClassObject self, Location loc) throws EvalException { + StringBuilder sb = new StringBuilder(); + printTextMessage(self, sb, 0, loc); + return sb.toString(); } - } - private void printSimpleTextMessage(String key, Object value, StringBuilder sb, - int indent, Location loc, String container) throws EvalException { - if (value instanceof ClassObject) { - print(sb, key + " {", indent); - printTextMessage((ClassObject) value, sb, indent + 1, loc); - print(sb, "}", indent); - } else if (value instanceof String) { - print(sb, key + ": \"" + escape((String) value) + "\"", indent); - } else if (value instanceof Integer) { - print(sb, key + ": " + value, indent); - } else if (value instanceof Boolean) { - // We're relying on the fact that Java converts Booleans to Strings in the same way - // as the protocol buffers do. - print(sb, key + ": " + value, indent); - } else { - throw new EvalException(loc, - "Invalid text format, expected a struct, a string, a bool, or an int but got a " - + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'"); + private void printTextMessage(ClassObject object, StringBuilder sb, + int indent, Location loc) throws EvalException { + for (String key : object.getKeys()) { + printTextMessage(key, object.getValue(key), sb, indent, loc); + } } - } - private void printTextMessage(String key, Object value, StringBuilder sb, - int indent, Location loc) throws EvalException { - if (value instanceof SkylarkList) { - for (Object item : ((SkylarkList) value)) { - // TODO(bazel-team): There should be some constraint on the fields of the structs - // in the same list but we ignore that for now. - printSimpleTextMessage(key, item, sb, indent, loc, "list element in struct field"); + private void printSimpleTextMessage(String key, Object value, StringBuilder sb, + int indent, Location loc, String container) throws EvalException { + if (value instanceof ClassObject) { + print(sb, key + " {", indent); + printTextMessage((ClassObject) value, sb, indent + 1, loc); + print(sb, "}", indent); + } else if (value instanceof String) { + print(sb, key + ": \"" + escape((String) value) + "\"", indent); + } else if (value instanceof Integer) { + print(sb, key + ": " + value, indent); + } else if (value instanceof Boolean) { + // We're relying on the fact that Java converts Booleans to Strings in the same way + // as the protocol buffers do. + print(sb, key + ": " + value, indent); + } else { + throw new EvalException(loc, + "Invalid text format, expected a struct, a string, a bool, or an int but got a " + + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'"); } - } else { - printSimpleTextMessage(key, value, sb, indent, loc, "struct field"); } - } - private String escape(String string) { - // TODO(bazel-team): use guava's SourceCodeEscapers when it's released. - return string.replace("\"", "\\\"").replace("\n", "\\n"); - } + private void printTextMessage(String key, Object value, StringBuilder sb, + int indent, Location loc) throws EvalException { + if (value instanceof SkylarkList) { + for (Object item : ((SkylarkList) value)) { + // TODO(bazel-team): There should be some constraint on the fields of the structs + // in the same list but we ignore that for now. + printSimpleTextMessage(key, item, sb, indent, loc, "list element in struct field"); + } + } else { + printSimpleTextMessage(key, value, sb, indent, loc, "struct field"); + } + } - private void print(StringBuilder sb, String text, int indent) { - for (int i = 0; i < indent; i++) { - sb.append(" "); + private String escape(String string) { + // TODO(bazel-team): use guava's SourceCodeEscapers when it's released. + return string.replace("\"", "\\\"").replace("\n", "\\n"); } + + private void print(StringBuilder sb, String text, int indent) { + for (int i = 0; i < indent; i++) { + sb.append(" "); + } sb.append(text); sb.append("\n"); - } - }; + } + }; + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkRuleClassFunctions.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java index 0019fe8683..03e2bce974 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java @@ -32,18 +32,19 @@ import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Type.ConversionException; +import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Label; -import com.google.devtools.build.lib.syntax.SkylarkFunction; -import com.google.devtools.build.lib.syntax.SkylarkFunction.SimpleSkylarkFunction; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkNestedSet; import com.google.devtools.build.lib.syntax.SkylarkSignature; import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; +import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor; import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -106,75 +107,79 @@ public class SkylarkRuleImplementationFunctions { doc = "sets the map of input manifests files; " + "they are typicially generated by the command_helper")}, useLocation = true) - private static final SkylarkFunction createSpawnAction = - new SimpleSkylarkFunction("action") { - - @Override - public Object call(Map<String, Object> params, Location loc) throws EvalException, - ConversionException { - SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self"); + private static final BuiltinFunction createSpawnAction = new BuiltinFunction("action") { + public Environment.NoneType invoke( + SkylarkRuleContext ctx, + SkylarkList outputs, + SkylarkList inputs, + Object executableO, + SkylarkList arguments, + Object mnemonicO, + Object commandO, + Object progressMessage, + Boolean useDefaultShellEnv, + Object envO, + Object executionRequirementsO, + Object inputManifestsO, + Location loc) throws EvalException, ConversionException { SpawnAction.Builder builder = new SpawnAction.Builder(); // TODO(bazel-team): builder still makes unnecessary copies of inputs, outputs and args. - builder.addInputs(castList(params.get("inputs"), Artifact.class)); - builder.addOutputs(castList(params.get("outputs"), Artifact.class)); - builder.addArguments(castList(params.get("arguments"), String.class)); - if (params.containsKey("executable")) { - Object exe = params.get("executable"); - if (exe instanceof Artifact) { - Artifact executable = (Artifact) exe; + builder.addInputs(castList(inputs, Artifact.class)); + builder.addOutputs(castList(outputs, Artifact.class)); + builder.addArguments(castList(arguments, String.class)); + if (executableO != Environment.NONE) { + if (executableO instanceof Artifact) { + Artifact executable = (Artifact) executableO; builder.addInput(executable); FilesToRunProvider provider = ctx.getExecutableRunfiles(executable); if (provider == null) { - builder.setExecutable((Artifact) exe); + builder.setExecutable(executable); } else { builder.setExecutable(provider); } - } else if (exe instanceof PathFragment) { - builder.setExecutable((PathFragment) exe); + } else if (executableO instanceof PathFragment) { + builder.setExecutable((PathFragment) executableO); } else { throw new EvalException(loc, "expected file or PathFragment for " - + "executable but got " + EvalUtils.getDataTypeName(exe) + " instead"); + + "executable but got " + EvalUtils.getDataTypeName(executableO) + " instead"); } } - if (params.containsKey("command") == params.containsKey("executable")) { + if ((commandO == Environment.NONE) == (executableO == Environment.NONE)) { throw new EvalException(loc, "You must specify either 'command' or 'executable' argument"); } - if (params.containsKey("command")) { - Object command = params.get("command"); - if (command instanceof String) { - builder.setShellCommand((String) command); - } else if (command instanceof SkylarkList) { - SkylarkList commandList = (SkylarkList) command; + if (commandO != Environment.NONE) { + if (commandO instanceof String) { + builder.setShellCommand((String) commandO); + } else if (commandO instanceof SkylarkList) { + SkylarkList commandList = (SkylarkList) commandO; if (commandList.size() < 3) { throw new EvalException(loc, "'command' list has to be of size at least 3"); } builder.setShellCommand(castList(commandList, String.class, "command")); } else { throw new EvalException(loc, "expected string or list of strings for " - + "command instead of " + EvalUtils.getDataTypeName(command)); + + "command instead of " + EvalUtils.getDataTypeName(commandO)); } } - if (params.containsKey("mnemonic")) { - builder.setMnemonic((String) params.get("mnemonic")); + if (mnemonicO != Environment.NONE) { + builder.setMnemonic((String) mnemonicO); } - if (params.containsKey("env")) { + if (envO != Environment.NONE) { builder.setEnvironment(ImmutableMap.copyOf( - castMap(params.get("env"), String.class, String.class, "env"))); + castMap(envO, String.class, String.class, "env"))); } - if (params.containsKey("progress_message")) { - builder.setProgressMessage((String) params.get("progress_message")); + if (progressMessage != Environment.NONE) { + builder.setProgressMessage((String) progressMessage); } - if (params.containsKey("use_default_shell_env") - && EvalUtils.toBoolean(params.get("use_default_shell_env"))) { + if (EvalUtils.toBoolean(useDefaultShellEnv)) { builder.useDefaultShellEnvironment(); } - if (params.containsKey("execution_requirements")) { + if (executionRequirementsO != Environment.NONE) { builder.setExecutionInfo(ImmutableMap.copyOf(castMap( - params.get("execution_requirements"), - String.class, String.class, "execution_requirements"))); + executionRequirementsO, String.class, String.class, "execution_requirements"))); } - if (params.containsKey("input_manifests")) { - for (Map.Entry<PathFragment, Artifact> entry : castMap(params.get("input_manifests"), + if (inputManifestsO != Environment.NONE) { + for (Map.Entry<PathFragment, Artifact> entry : castMap(inputManifestsO, PathFragment.class, Artifact.class, "input manifest file map").entrySet()) { builder.addInputManifest(entry.getValue(), entry.getKey()); } @@ -195,25 +200,19 @@ public class SkylarkRuleImplementationFunctions { @Param(name = "output", type = Artifact.class, doc = "the output file"), @Param(name = "content", type = String.class, doc = "the contents of the file")}, optionalPositionals = { - @Param(name = "executable", type = Boolean.class, + @Param(name = "executable", type = Boolean.class, defaultValue = "False", doc = "whether the output file should be executable (default is False)")}) - private static final SkylarkFunction createFileWriteAction = - new SimpleSkylarkFunction("file_action") { - - @Override - public Object call(Map<String, Object> params, Location loc) throws EvalException, - ConversionException { - SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self"); - boolean executable = params.containsKey("executable") && (Boolean) params.get("executable"); - FileWriteAction action = new FileWriteAction( - ctx.getRuleContext().getActionOwner(), - (Artifact) params.get("output"), - (String) params.get("content"), - executable); - ctx.getRuleContext().registerAction(action); - return action; - } - }; + private static final BuiltinFunction createFileWriteAction = + new BuiltinFunction("file_action") { + public FileWriteAction invoke(SkylarkRuleContext ctx, + Artifact output, String content, Boolean executable) + throws EvalException, ConversionException { + FileWriteAction action = new FileWriteAction( + ctx.getRuleContext().getActionOwner(), output, content, executable); + ctx.getRuleContext().registerAction(action); + return action; + } + }; @SkylarkSignature(name = "template_action", doc = "Creates a template expansion action.", @@ -231,30 +230,26 @@ public class SkylarkRuleImplementationFunctions { optionalNamedOnly = { @Param(name = "executable", type = Boolean.class, doc = "whether the output file should be executable (default is False)")}) - private static final SkylarkFunction createTemplateAction = - new SimpleSkylarkFunction("template_action") { - - @Override - public Object call(Map<String, Object> params, Location loc) throws EvalException, - ConversionException { - SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self"); - ImmutableList.Builder<Substitution> substitutions = ImmutableList.builder(); - for (Map.Entry<String, String> substitution : castMap( - params.get("substitutions"), String.class, String.class, "substitutions").entrySet()) { - substitutions.add(Substitution.of(substitution.getKey(), substitution.getValue())); - } - - boolean executable = params.containsKey("executable") && (Boolean) params.get("executable"); - TemplateExpansionAction action = new TemplateExpansionAction( - ctx.getRuleContext().getActionOwner(), - (Artifact) params.get("template"), - (Artifact) params.get("output"), - substitutions.build(), - executable); - ctx.getRuleContext().registerAction(action); - return action; - } - }; + private static final BuiltinFunction createTemplateAction = + new BuiltinFunction("template_action", Arrays.<Object>asList(false)) { + public TemplateExpansionAction invoke(SkylarkRuleContext ctx, + Artifact template, Artifact output, Map<?, ?> substitutionsO, Boolean executable) + throws EvalException, ConversionException { + ImmutableList.Builder<Substitution> substitutions = ImmutableList.builder(); + for (Map.Entry<String, String> substitution : castMap( + substitutionsO, String.class, String.class, "substitutions").entrySet()) { + substitutions.add(Substitution.of(substitution.getKey(), substitution.getValue())); + } + TemplateExpansionAction action = new TemplateExpansionAction( + ctx.getRuleContext().getActionOwner(), + template, + output, + substitutions.build(), + executable); + ctx.getRuleContext().registerAction(action); + return action; + } + }; /** * A built in Skylark helper function to access the @@ -267,11 +262,9 @@ public class SkylarkRuleImplementationFunctions { doc = "the configured target which provides the provider"), @Param(name = "type", type = String.class, doc = "the class type of the provider")}, useLocation = true) - private static final SkylarkFunction provider = new SimpleSkylarkFunction("provider") { - @Override - public Object call(Map<String, Object> params, Location loc) throws EvalException { - TransitiveInfoCollection target = (TransitiveInfoCollection) params.get("target"); - String type = (String) params.get("type"); + private static final BuiltinFunction provider = new BuiltinFunction("provider") { + public Object invoke(TransitiveInfoCollection target, String type, + Location loc) throws EvalException { try { Class<?> classType = SkylarkRuleContext.classCache.get(type); Class<? extends TransitiveInfoProvider> convertedClass = @@ -309,24 +302,22 @@ public class SkylarkRuleImplementationFunctions { doc = "Whether to collect the default " + "runfiles from the dependencies in srcs, data and deps attributes.")}, useLocation = true) - private static final SkylarkFunction runfiles = new SimpleSkylarkFunction("runfiles") { - @Override - public Object call(Map<String, Object> params, Location loc) throws EvalException, - ConversionException { - SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self"); + private static final BuiltinFunction runfiles = new BuiltinFunction("runfiles") { + public Runfiles invoke(SkylarkRuleContext ctx, SkylarkList files, Object transitiveFiles, + Boolean collectData, Boolean collectDefault, + Location loc) throws EvalException, ConversionException { Runfiles.Builder builder = new Runfiles.Builder(); - if (params.containsKey("collect_data") && (Boolean) params.get("collect_data")) { + if (EvalUtils.toBoolean(collectData)) { builder.addRunfiles(ctx.getRuleContext(), RunfilesProvider.DATA_RUNFILES); } - if (params.containsKey("collect_default") && (Boolean) params.get("collect_default")) { + if (EvalUtils.toBoolean(collectDefault)) { builder.addRunfiles(ctx.getRuleContext(), RunfilesProvider.DEFAULT_RUNFILES); } - if (params.containsKey("files")) { - builder.addArtifacts(castList(params.get("files"), Artifact.class)); + if (!files.isEmpty()) { + builder.addArtifacts(castList(files, Artifact.class)); } - if (params.containsKey("transitive_files")) { - builder.addTransitiveArtifacts(((SkylarkNestedSet) params.get("transitive_files")) - .getSet(Artifact.class)); + if (transitiveFiles != Environment.NONE) { + builder.addTransitiveArtifacts(((SkylarkNestedSet) transitiveFiles).getSet(Artifact.class)); } return builder.build(); } @@ -342,20 +333,22 @@ public class SkylarkRuleImplementationFunctions { @Param(name = "label_dict", type = Map.class, defaultValue = "{}", doc = "dictionary of resolved labels and the corresponding list of Files " + "(a dict of Label : list of Files)")}) - private static final SkylarkFunction createCommandHelper = - new SimpleSkylarkFunction("command_helper") { - @SuppressWarnings("unchecked") - @Override - protected Object call(Map<String, Object> params, Location loc) - throws ConversionException, EvalException { - SkylarkRuleContext ctx = (SkylarkRuleContext) params.get("self"); - return new CommandHelper(ctx.getRuleContext(), - AnalysisUtils.getProviders( - castList(params.get("tools"), TransitiveInfoCollection.class), - FilesToRunProvider.class), - // TODO(bazel-team): this cast to Map is unchecked and is not safe. - // The best way to fix this probably is to convert CommandHelper to Skylark. - ImmutableMap.copyOf((Map<Label, Iterable<Artifact>>) params.get("label_dict"))); - } - }; + private static final BuiltinFunction createCommandHelper = new BuiltinFunction("command_helper") { + @SuppressWarnings("unchecked") + // TODO(bazel-team): this cast to Map is unchecked and is not safe. + // The best way to fix this probably is to convert CommandHelper to Skylark. + public CommandHelper invoke( + SkylarkRuleContext ctx, SkylarkList tools, Map<Label, Iterable<Artifact>> labelDict) + throws ConversionException, EvalException { + return new CommandHelper(ctx.getRuleContext(), + AnalysisUtils.getProviders( + castList(tools, TransitiveInfoCollection.class), + FilesToRunProvider.class), + ImmutableMap.copyOf(labelDict)); + } + }; + + static { + SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkRuleImplementationFunctions.class); + } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java index 08ea8ee3c1..af33276aa7 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinFunction.java @@ -205,8 +205,8 @@ public class BuiltinFunction extends BaseFunction { Preconditions.checkState(!isConfigured()); // must not be configured yet enforcedArgumentTypes = new ArrayList<>(); this.extraArgs = SkylarkSignatureProcessor.getExtraArgs(annotation); - super.configure(annotation); this.returnType = annotation.returnType(); + super.configure(annotation); } // finds the method and makes it accessible (which is needed to find it, and later to use it) @@ -273,7 +273,10 @@ public class BuiltinFunction extends BaseFunction { if (type == HackHackEitherList.class) { type = Object.class; } - Preconditions.checkArgument(type == invokeMethod.getReturnType()); + Class<?> methodReturnType = invokeMethod.getReturnType(); + Preconditions.checkArgument(type == methodReturnType, + "signature for function %s says it returns %s but its invoke method returns %s", + getName(), returnType, methodReturnType); } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java index 2e94be8706..bff06d5348 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkCallbackFunction.java @@ -15,17 +15,16 @@ package com.google.devtools.build.lib.syntax; import com.google.common.collect.ImmutableList; - /** * A helper class for calling Skylark functions from Java. */ public class SkylarkCallbackFunction { - private final UserDefinedFunction callback; + private final Function callback; private final FuncallExpression ast; private final SkylarkEnvironment funcallEnv; - public SkylarkCallbackFunction(UserDefinedFunction callback, FuncallExpression ast, + public SkylarkCallbackFunction(Function callback, FuncallExpression ast, SkylarkEnvironment funcallEnv) { this.callback = callback; this.ast = ast; diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java index b264dd699b..11e35227b7 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java @@ -205,8 +205,8 @@ public class SkylarkEnvironment extends Environment implements Serializable { List<Class<?>> modulesToRemove = new ArrayList<>(); for (Map.Entry<String, Object> entry : env.entrySet()) { Object object = entry.getValue(); - if (object instanceof SkylarkFunction) { - if (((SkylarkFunction) object).isOnlyLoadingPhase()) { + if (object instanceof BaseFunction) { + if (((BaseFunction) object).isOnlyLoadingPhase()) { objectsToRemove.add(entry.getKey()); } } else if (object.getClass().isAnnotationPresent(SkylarkModule.class)) { |