Skip to content

Commit

Permalink
Introduce Param.legacyNamed() to handle migration from @SkylarkSignat…
Browse files Browse the repository at this point in the history
…ure to @SkylarkCallable.

@SkylarkSignature.parameters() treat named()=true for parameters regardless of whether named() was actually set to true, and thus for ease of migration we mark migrated parameters as legacyNamed() = true, which is currently semantically equivalent to named() = true.
We make a distinction so that it's easier to bulk fix these later.

RELNOTES: None.
PiperOrigin-RevId: 192477405
  • Loading branch information
c-parsons authored and Copybara-Service committed Apr 11, 2018
1 parent 51ae67a commit 4baafac
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@
*/
boolean named() default false;

/**
* If this true, {@link #named} should be treated as true.
*
* <p>This indicates this parameter is part of a {@link SkylarkCallable} method which
* was migrated from {@link SkylarkSignature}. Due to a pre-migration bug, all parameters were
* treated as if {@link #named} was true, even if it was false. To prevent breakages during
* migration, the interpreter can continue to treat these parameters as named. This is distinct
* from {@link #named}, however, so that a bulk fix/cleanup will be easier later.
*/
// TODO(b/77902276): Remove this after a bulk cleanup/fix.
boolean legacyNamed() default false;

/**
* If true, the parameter may be specified as a positional parameter. For example for an integer
* positional parameter {@code foo} of a method {@code bar}, then the method call will look like
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,18 @@ private void verifyNotStructFieldWithParams(
}
}

private static boolean isParamNamed(Param param) {
return param.named() || param.legacyNamed();
}

private void verifyParamSemantics(ExecutableElement methodElement, SkylarkCallable annotation)
throws SkylarkCallableProcessorException {
boolean allowPositionalNext = true;
boolean allowPositionalOnlyNext = true;
boolean allowNonDefaultPositionalNext = true;

for (Param parameter : annotation.parameters()) {
if ((!parameter.positional()) && (!parameter.named())) {
if ((!parameter.positional()) && (!isParamNamed(parameter))) {
throw new SkylarkCallableProcessorException(
methodElement,
String.format("Parameter '%s' must be either positional or named",
Expand Down Expand Up @@ -180,7 +184,7 @@ private void verifyParamSemantics(ExecutableElement methodElement, SkylarkCallab
+ "non-positonal parameters",
parameter.name()));
}
if (!parameter.named() && !allowPositionalOnlyNext) {
if (!isParamNamed(parameter) && !allowPositionalOnlyNext) {
throw new SkylarkCallableProcessorException(
methodElement,
String.format(
Expand All @@ -205,7 +209,7 @@ private void verifyParamSemantics(ExecutableElement methodElement, SkylarkCallab
// No positional parameters can come after this parameter.
allowPositionalNext = false;
}
if (parameter.named()) {
if (isParamNamed(parameter)) {
// No positional-only parameters can come after this parameter.
allowPositionalOnlyNext = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ private static SkylarkType getType(Param param) {
return result;
}

private static boolean isParamNamed(Param param) {
return param.named() || param.legacyNamed();
}

/**
* Constructs the parameters list to actually pass to the method, filling with default values if
* any. If there is a type or argument mismatch, returns a result containing an error message.
Expand Down Expand Up @@ -557,13 +561,14 @@ private ArgumentListConversionResult convertArgumentList(
"expected value of type '%s' for parameter '%s'",
type.toString(), param.name()));
}
if (param.named() && keys.contains(param.name())) {
if (isParamNamed(param) && keys.contains(param.name())) {
return ArgumentListConversionResult.fromError(
String.format("got multiple values for keyword argument '%s'", param.name()));
}
argIndex++;
} else { // No more positional arguments, or no more positional parameters.
if (param.named() && keys.remove(param.name())) { // Param specified by keyword argument.
if (isParamNamed(param) && keys.remove(param.name())) {
// Param specified by keyword argument.
value = kwargs.get(param.name());
if (!type.contains(value)) {
return ArgumentListConversionResult.fromError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForC
annotation.extraKeywords(), defaultValues, paramDoc, enforcedTypesList);
}

private static boolean isParamNamed(Param param) {
return param.named() || param.legacyNamed();
}

private static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
String name, boolean documented,
ImmutableList<Parameter<Object, SkylarkType>> mandatoryPositionals,
Expand All @@ -133,7 +137,7 @@ private static FunctionSignature.WithValues<Object, SkylarkType> getSignatureFor
for (Param param : parameters) {
boolean mandatory = param.defaultValue() != null && param.defaultValue().isEmpty();
Object defaultValue = mandatory ? null : getDefaultValue(param, defaultValuesIterator);
if (param.named() && !param.positional() && !named) {
if (isParamNamed(param) && !param.positional() && !named) {
named = true;
@Nullable Param starParam = null;
if (extraPositionals != null && !extraPositionals.name().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@ public Map<String, List<String>> stringListDict() {
return ImmutableMap.of("a", ImmutableList.of("b", "c"));
}

@SkylarkCallable(
name = "legacy_method",
documented = false,
parameters = {
@Param(name = "pos", positional = true, type = Boolean.class),
@Param(name = "legacyNamed", type = Boolean.class, positional = true, named = false,
legacyNamed = true),
@Param(name = "named", type = Boolean.class, positional = false, named = true),
})
public String legacyMethod(Boolean pos, Boolean legacyNamed, Boolean named) {
return "legacy_method("
+ pos
+ ", "
+ legacyNamed
+ ", "
+ named
+ ")";
}

@SkylarkCallable(
name = "with_params",
Expand Down Expand Up @@ -1020,6 +1038,36 @@ public void testProxyMethodsObject() throws Exception {
.testLookup("b", "with_params(1, true, false, true, false, a)");
}

@Test
public void testLegacyNamed() throws Exception {
new SkylarkTest()
.update("mock", new Mock())
.setUp(
"b = mock.legacy_method(True, legacyNamed=True, named=True)")
.testLookup("b", "legacy_method(true, true, true)");

new SkylarkTest()
.update("mock", new Mock())
.setUp(
"b = mock.legacy_method(True, True, named=True)")
.testLookup("b", "legacy_method(true, true, true)");

// Verify legacyNamed also works with proxy method objects.
new SkylarkTest()
.update("mock", new Mock())
.setUp(
"m = mock.proxy_methods_object()",
"b = m.legacy_method(True, legacyNamed=True, named=True)")
.testLookup("b", "legacy_method(true, true, true)");

new SkylarkTest()
.update("mock", new Mock())
.setUp(
"m = mock.proxy_methods_object()",
"b = m.legacy_method(True, True, named=True)")
.testLookup("b", "legacy_method(true, true, true)");
}

/**
* This test verifies an error is raised when a method parameter is set both positionally and
* by name.
Expand Down Expand Up @@ -1758,6 +1806,7 @@ public void testDirFindsJavaObjectStructFieldsAndMethods() throws Exception {
"dir(mock)",
"function",
"is_empty",
"legacy_method",
"nullfunc_failing",
"nullfunc_working",
"proxy_methods_object",
Expand Down

0 comments on commit 4baafac

Please sign in to comment.