Stream inlining: more precise source/terminal handling

Fixes IDEA-176129 Provide Control flow related inspection for Stream.anyMatch() / Stream.allMatch() / Stream.noneMatch()
This commit is contained in:
Tagir Valeev
2017-08-14 17:27:36 +07:00
parent 152dad886c
commit 4a48b57906
4 changed files with 373 additions and 34 deletions
@@ -15,10 +15,8 @@
*/
package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInspection.dataFlow.CFGBuilder;
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
import com.intellij.codeInspection.dataFlow.NullabilityProblem;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
@@ -40,8 +38,14 @@ public class StreamChainInliner implements CallInliner {
"average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet"};
private static final CallMatcher TERMINAL_CALL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, TERMINALS);
private static final CallMatcher LAMBDA_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch",
"noneMatch", "forEach", "forEachOrdered").parameterCount(1);
private static final CallMatcher FOR_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1);
private static final CallMatcher MATCH_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch",
"noneMatch").parameterCount(1);
private static final CallMatcher SUM_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "sum", "count").parameterCount(0);
private static final CallMatcher OPTIONAL_TERMINAL =
anyOf(instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "min", "max").parameterCount(0),
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "min", "max", "reduce").parameterCount(1),
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "findFirst", "findAny").parameterCount(0));
private static final CallMatcher SKIP_STEP =
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "unordered", "parallel", "sequential", "sorted").parameterCount(0);
@@ -61,6 +65,23 @@ public class StreamChainInliner implements CallInliner {
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "generate").parameterCount(1),
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "generate").parameterCount(1),
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "generate").parameterCount(1));
private static final CallMatcher STREAM_EMPTY = anyOf(
staticCall(JAVA_UTIL_STREAM_STREAM, "empty").parameterCount(0),
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "empty").parameterCount(0),
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "empty").parameterCount(0),
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "empty").parameterCount(0));
private static final CallMatcher STREAM_OF = anyOf(
staticCall(JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T"),
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "of").parameterTypes("int"),
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "of").parameterTypes("long"),
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "of").parameterTypes("double"));
private static final CallMatcher STREAM_OF_ARRAY = anyOf(
staticCall(JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T[]"),
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "of").parameterTypes("int[]"),
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "of").parameterTypes("long[]"),
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "of").parameterTypes("double[]"));
private static final CallMatcher ARRAYS_STREAM = staticCall(JAVA_UTIL_ARRAYS, "stream").parameterCount(1);
private static final CallMatcher COLLECTION_STREAM = instanceCall(JAVA_UTIL_COLLECTION, "stream").parameterCount(0);
private static final CallMapper<UnaryOperator<Step>> INTERMEDIATE_STEP_MAPPER = new CallMapper<UnaryOperator<Step>>()
.register(FILTER, (PsiMethodCallExpression call) -> (Step next) -> new FilterStep(call, next))
@@ -71,6 +92,12 @@ public class StreamChainInliner implements CallInliner {
.register(BOXED, (PsiMethodCallExpression call) -> (Step next) -> new BoxedStep(call, next))
.register(STATE_FILTER, (PsiMethodCallExpression call) -> (Step next) -> new StateFilterStep(call, next));
private static final CallMapper<Step> TERMINAL_STEP_MAPPER = new CallMapper<Step>()
.register(FOR_TERMINAL, LambdaTerminalStep::new)
.register(MATCH_TERMINAL, MatchTerminalStep::new)
.register(SUM_TERMINAL, SumTerminalStep::new)
.register(OPTIONAL_TERMINAL, OptionalTerminalStep::new);
static abstract class Step {
final Step myNext;
final @NotNull PsiMethodCallExpression myCall;
@@ -92,6 +119,16 @@ public class StreamChainInliner implements CallInliner {
}
abstract void iteration(CFGBuilder builder);
void pushResult(CFGBuilder builder) {
if (myNext != null) {
myNext.pushResult(builder);
}
else {
builder.push(builder.getFactory()
.createTypeValue(myCall.getType(), DfaPsiUtil.getElementNullability(myCall.getType(), myCall.resolveMethod())));
}
}
}
static class UnknownTerminalStep extends Step {
@@ -114,6 +151,31 @@ public class StreamChainInliner implements CallInliner {
}
}
static abstract class TerminalStep extends Step {
PsiVariable myResult;
TerminalStep(@NotNull PsiMethodCallExpression call, PsiExpression function) {
super(call, null, function);
}
@Override
void before(CFGBuilder builder) {
myResult = builder.createTempVariable(myCall.getType());
builder.pushVariable(myResult)
.chain(this::pushInitialValue)
.assign()
.pop()
.chain(super::before);
}
protected abstract void pushInitialValue(CFGBuilder builder);
@Override
void pushResult(CFGBuilder builder) {
builder.push(builder.getFactory().getVarFactory().createVariableValue(myResult, false));
}
}
static class LambdaTerminalStep extends Step {
LambdaTerminalStep(@NotNull PsiMethodCallExpression call) {
super(call, null, call.getArgumentList().getExpressions()[0]);
@@ -125,6 +187,74 @@ public class StreamChainInliner implements CallInliner {
}
}
static class SumTerminalStep extends TerminalStep {
SumTerminalStep(@NotNull PsiMethodCallExpression call) {
super(call, null);
}
@Override
protected void pushInitialValue(CFGBuilder builder) {
PsiType type = myCall.getType();
Object value = null;
if (PsiType.INT.equals(type)) {
value = 0;
}
else if (PsiType.LONG.equals(type)) {
value = 0L;
}
else if (PsiType.DOUBLE.equals(type)) {
value = 0.0;
}
builder.push(builder.getFactory().getConstFactory().createFromValue(value, type, null));
}
@Override
void iteration(CFGBuilder builder) {
builder.pushVariable(myResult).pushUnknown().assign().splice(2);
}
}
static class OptionalTerminalStep extends TerminalStep {
OptionalTerminalStep(@NotNull PsiMethodCallExpression call) {
super(call, ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()));
}
@Override
protected void pushInitialValue(CFGBuilder builder) {
builder.push(builder.getFactory().getOptionalFactory().getOptional(false));
}
@Override
void iteration(CFGBuilder builder) {
if (myFunction != null) {
builder.pushUnknown().invokeFunction(2, myFunction);
}
builder.pushVariable(myResult).push(builder.getFactory().getOptionalFactory().getOptional(true)).assign().splice(2);
}
}
static class MatchTerminalStep extends TerminalStep {
MatchTerminalStep(@NotNull PsiMethodCallExpression call) {
super(call, call.getArgumentList().getExpressions()[0]);
}
@Override
protected void pushInitialValue(CFGBuilder builder) {
builder.push(builder.getFactory().getBoolean(!"anyMatch".equals(myCall.getMethodExpression().getReferenceName())));
}
@Override
void iteration(CFGBuilder builder) {
builder.invokeFunction(1, myFunction)
.ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName()))
.pushVariable(myResult)
.push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName())))
.assign()
.pop()
.endIf();
}
}
static class FilterStep extends Step {
FilterStep(@NotNull PsiMethodCallExpression call, Step next) {
super(call, next, call.getArgumentList().getExpressions()[0]);
@@ -178,9 +308,22 @@ public class StreamChainInliner implements CallInliner {
streamSource = body;
chain = next;
if (body instanceof PsiMethodCallExpression) {
chain = buildChain((PsiMethodCallExpression)body, next);
if (chain != next) {
Step filteredNext = new Step(call, next, null) {
@Override
void before(CFGBuilder builder) {
// skip following steps: their before behavior is added before the main loop
}
@Override
void iteration(CFGBuilder builder) {
myNext.iteration(builder);
}
};
chain = buildChain((PsiMethodCallExpression)body, filteredNext);
if (chain != filteredNext) {
streamSource = chain.myCall.getMethodExpression().getQualifierExpression();
} else {
streamSource = body;
}
}
}
@@ -300,15 +443,10 @@ public class StreamChainInliner implements CallInliner {
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
Step terminalStep = createTerminalStep(call);
Step firstStep = buildChain(qualifierCall, terminalStep);
if (firstStep == terminalStep) {
// Do not handle specially case when only terminal operation is known: at least one intermediate op should be known as well
return false;
}
PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression();
if (originalQualifier == null) return false;
buildStreamCFG(builder, firstStep, originalQualifier);
builder.push(
builder.getFactory().createTypeValue(call.getType(), DfaPsiUtil.getElementNullability(call.getType(), call.resolveMethod())));
firstStep.pushResult(builder);
return true;
}
@@ -326,19 +464,92 @@ public class StreamChainInliner implements CallInliner {
.assign()
.chain(firstStep::iteration)
.endWhileUnknown();
} else {
builder
.pushExpression(originalQualifier)
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
.pop()
.chain(firstStep::before)
.doWhile()
.pushVariable(builder.createTempVariable(inType))
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
.assign()
.chain(firstStep::iteration)
.endWhileUnknown();
return;
}
boolean empty = STREAM_EMPTY.test(sourceCall);
boolean array = ARRAYS_STREAM.test(sourceCall);
boolean single = STREAM_OF.test(sourceCall);
if (STREAM_OF_ARRAY.test(sourceCall)) {
PsiExpression[] args = sourceCall.getArgumentList().getExpressions();
if(args.length == 0) {
empty = true;
} else if(args.length == 1) {
if(MethodCallUtils.isVarArgCall(sourceCall)) {
single = true;
} else {
array = true;
}
} else {
builder
.pushExpression(originalQualifier)
.pop()
.chain(firstStep::before)
.chain(b -> makeMainLoop(b, firstStep, inType));
return;
}
}
if (empty) {
builder.chain(firstStep::before); // skip loop at all
return;
}
if (single) {
// exactly single element
builder
.pushExpression(sourceCall.getArgumentList().getExpressions()[0])
.chain(firstStep::before)
.chain(firstStep::iteration);
return;
}
if (array) {
PsiExpression qualifierExpression = sourceCall.getArgumentList().getExpressions()[0];
DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression);
if (qualifierValue != null) {
builder.pushExpression(qualifierExpression)
.chain(firstStep::before)
.checkNotNull(qualifierExpression, NullabilityProblem.passingNullableToNotNullParameter)
.pop()
.push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue))
.push(builder.getFactory().getConstFactory().createFromValue(0, PsiType.INT, null))
.ifCondition(JavaTokenType.GT)
.chain(b -> makeMainLoop(b, firstStep, inType))
.endIf();
return;
}
}
if (COLLECTION_STREAM.test(sourceCall)) {
PsiExpression qualifierExpression = sourceCall.getMethodExpression().getQualifierExpression();
DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression);
if (qualifierValue != null) {
builder.pushExpression(qualifierExpression)
.chain(firstStep::before)
.checkNotNull(sourceCall, NullabilityProblem.callNPE)
.pop()
.push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue))
.push(builder.getFactory().getConstFactory().createFromValue(0, PsiType.INT, null))
.ifCondition(JavaTokenType.GT)
.chain(b -> makeMainLoop(b, firstStep, inType))
.endIf();
return;
}
}
builder
.pushExpression(originalQualifier)
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
.pop()
.chain(firstStep::before)
.pushUnknown()
.ifConditionIs(true)
.chain(b -> makeMainLoop(b, firstStep, inType))
.endIf();
}
private static void makeMainLoop(CFGBuilder builder, Step firstStep, PsiType inType) {
builder.doWhile()
.pushVariable(builder.createTempVariable(inType))
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
.assign()
.chain(firstStep::iteration)
.endWhileUnknown();
}
static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) {
@@ -361,9 +572,7 @@ public class StreamChainInliner implements CallInliner {
}
private static Step createTerminalStep(PsiMethodCallExpression call) {
if (LAMBDA_TERMINAL.test(call)) {
return new LambdaTerminalStep(call);
}
return new UnknownTerminalStep(call);
Step step = TERMINAL_STEP_MAPPER.mapFirst(call);
return step == null ? new UnknownTerminalStep(call) : step;
}
}
@@ -10,19 +10,23 @@ public class StreamInlining {
list.stream().map(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>).forEach(System.out::println);
list.stream().flatMap(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>).forEach(System.out::println);
list.stream().filter(x -> x != null).forEach(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
List<String> l = null;
l.<warning descr="Method invocation 'stream' may produce 'java.lang.NullPointerException'">stream</warning>().count();
int[] arr = null;
Arrays.stream(<warning descr="Argument 'arr' might be null">arr</warning>).count();
Stream<String> stream = null;
stream.<warning descr="Method invocation 'filter' may produce 'java.lang.NullPointerException'">filter</warning>(x -> x != null).forEach(System.out::println);
}
void testMethodRef(List<String> list, int[] data) {
if(list.stream().map(String::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
if(<warning descr="Condition 'list.stream().map(String::new).anyMatch(x -> x == null)' is always 'false'">list.stream().map(String::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>) {
System.out.println("never");
}
if(Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
if(<warning descr="Condition 'Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> x == null)' is always 'false'">Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>) {
System.out.println("never");
}
list.stream().filter(Objects::isNull).map(<warning descr="Method reference invocation 'String::trim' may produce 'java.lang.NullPointerException'">String::trim</warning>).forEach(System.out::println);
if(list.stream().filter(Objects::nonNull).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
if(<warning descr="Condition 'list.stream().filter(Objects::nonNull).anyMatch(x -> x == null)' is always 'false'">list.stream().filter(Objects::nonNull).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)</warning>) {
System.out.println("never");
}
list.stream().map(Integer::valueOf).filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>).forEach(System.out::println);
@@ -77,7 +81,8 @@ public class StreamInlining {
is.boxed().filter(x -> <warning descr="Condition 'x != null' is always 'true'">x != null</warning>).forEach(s -> System.out.println(s));
}
boolean flatMap(List<String> list) {
boolean flatMap(List<String> list, List<List<String>> ll) {
System.out.println(ll.stream().flatMap(l -> l.stream()).count());
return list.stream().map(s -> s.isEmpty() ? null : s)
.flatMap(s -> Stream.of(s, s.<warning descr="Method invocation 'trim' may produce 'java.lang.NullPointerException'">trim</warning>()).filter(r -> r != null))
.anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>);
@@ -0,0 +1,124 @@
import java.util.*;
import java.util.stream.*;
public class StreamKnownSource {
void testFlatMap(List<String> list) {
long count = list.stream().<String>flatMap(s -> Stream.empty()).count();
if(<warning descr="Condition 'count > 0' is always 'false'">count > 0</warning>) {
System.out.println("Impossible");
}
}
void testEmptyStream(List<String> list) {
long result = Stream.empty().count();
if (<warning descr="Condition 'result > 0' is always 'false'">result > 0</warning>) {
System.out.println("Never");
}
int sum = list.stream().filter(Objects::isNull).filter(<warning descr="Method reference result is always 'false'">Objects::nonNull</warning>).mapToInt(String::length).sum();
if (<warning descr="Condition 'sum == 0' is always 'true'">sum == 0</warning>) {
System.out.println("Always");
}
Optional<String> any = Stream.<String>empty().map(String::trim).findAny();
if (<warning descr="Condition 'any.isPresent()' is always 'false'">any.isPresent()</warning>) {
System.out.println("Never");
}
Optional<String> any2 = Stream.of("x").map(String::trim).findAny();
if (<warning descr="Condition 'any2.isPresent()' is always 'true'">any2.isPresent()</warning>) {
System.out.println("Always");
}
Optional<String> any3 = list.stream().map(String::trim).findAny();
if (any3.isPresent()) {
System.out.println("Possible");
}
Optional<String> any4 = Stream.of("x").limit(list.size()).map(String::trim).findAny();
if (any4.isPresent()) {
System.out.println("Probably");
}
boolean emptyAll = Stream.empty().allMatch(Objects::nonNull);
if (<warning descr="Condition 'emptyAll' is always 'true'">emptyAll</warning>) {
System.out.println("True");
}
boolean emptyAny = Stream.empty().anyMatch(Objects::nonNull);
if (<warning descr="Condition 'emptyAny' is always 'false'">emptyAny</warning>) {
System.out.println("False");
}
boolean emptyNone = Stream.empty().noneMatch(Objects::nonNull);
if (<warning descr="Condition 'emptyNone' is always 'true'">emptyNone</warning>) {
System.out.println("True");
}
boolean allMatch = Stream.generate(() -> "foo").limit(10).allMatch(<warning descr="Method reference result is always 'false'">"bar"::equals</warning>);
if (allMatch) { // currently we're not aware that stream is non-empty (limit arg is not processed), and allMatch could be true for empty list
System.out.println("Who knows?");
}
Optional<String> min = Stream.<String>empty().min(Comparator.comparing(String::length));
if(<warning descr="Condition 'min.isPresent()' is always 'false'">min.isPresent()</warning>) {
System.out.println("Impossible");
}
}
void testSingleElement(String foo) {
Optional<String> notNull = Stream.of(foo).filter(Objects::nonNull).findAny();
if(<warning descr="Condition 'notNull.isPresent() && foo == null' is always 'false'">notNull.isPresent() && <warning descr="Condition 'foo == null' is always 'false' when reached">foo == null</warning></warning>) {
System.out.println("Impossible");
}
OptionalInt max = IntStream.of(1).max();
if(<warning descr="Condition 'max.isPresent()' is always 'true'">max.isPresent()</warning>) {
System.out.println("Always");
}
OptionalLong opt = LongStream.of(2).reduce(Long::sum);
if(<warning descr="Condition 'opt.isPresent()' is always 'true'">opt.isPresent()</warning>) {
System.out.println("Always");
}
}
void testStreamOf(int[] arr) {
if(Stream.of().count() > 0) {
System.out.println("Impossible");
}
if(<warning descr="Condition 'Stream.of(\"foo\", \"bar\").findFirst().isPresent()' is always 'true'">Stream.of("foo", "bar").findFirst().isPresent()</warning>) {
System.out.println("Always");
}
if(<warning descr="Condition 'DoubleStream.of(0.0, 1.0, 2.0, 3.0).map(x -> x*2).max().isPresent()' is always 'true'">DoubleStream.of(0.0, 1.0, 2.0, 3.0).map(x -> x*2).max().isPresent()</warning>) {
System.out.println("Always");
}
if(<warning descr="Condition 'IntStream.of(arr).filter(x -> x > 0).sum() > 0 && arr.length == 0' is always 'false'">IntStream.of(arr).filter(x -> x > 0).sum() > 0 && <warning descr="Condition 'arr.length == 0' is always 'false' when reached">arr.length == 0</warning></warning>) {
System.out.println("Impossible");
}
}
void testListSize(List<String> list, int[] arr) {
if (<warning descr="Condition 'arr.length == 0 && Arrays.stream(arr).anyMatch(x -> x > 0)' is always 'false'">arr.length == 0 && <warning descr="Condition 'Arrays.stream(arr).anyMatch(x -> x > 0)' is always 'false' when reached">Arrays.stream(arr).anyMatch(x -> x > 0)</warning></warning>) {
System.out.println("Impossible");
}
if (<warning descr="Condition 'Arrays.stream(arr).anyMatch(x -> x > 0) && arr.length == 0' is always 'false'">Arrays.stream(arr).anyMatch(x -> x > 0) && <warning descr="Condition 'arr.length == 0' is always 'false' when reached">arr.length == 0</warning></warning>) {
System.out.println("Impossible");
}
boolean empty = list.isEmpty();
String res = list.stream().map(String::trim).findFirst().orElse(null);
if(res == null && <warning descr="Condition 'empty' is always 'true' when reached">empty</warning>) {
System.out.println("res == null -> empty list");
}
Optional<String> first = list.stream().filter(Objects::nonNull).findFirst();
if (empty) {
System.out.println(first.<warning descr="'Optional.get()' without 'isPresent()' check">get</warning>());
}
}
// IDEA-176129
void foo(List<Long> list) {
if (!list.isEmpty()) {
return;
}
boolean hasNoNulls = list.stream().allMatch(Objects::nonNull);
if(<warning descr="Condition 'hasNoNulls' is always 'true'">hasNoNulls</warning>) {
System.out.println("Always");
}
}
}
@@ -183,6 +183,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
doTest();
}
public void testStreamInlining() { doTest(); }
public void testStreamKnownSource() { doTest(); }
public void testMethodVsExpressionTypeAnnotationConflict() {
setupCustomAnnotations("withTypeUse", "{ElementType.METHOD, ElementType.TYPE_USE}", myFixture);