diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java index ec1a1a61ecce..199c2d568088 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureAsAnonymousParameterEnhancer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,20 @@ */ package org.jetbrains.plugins.groovy.lang.psi.typeEnhancers; -import com.intellij.psi.PsiSubstitutor; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiType; import com.intellij.psi.PsiWildcardType; +import com.intellij.psi.util.MethodSignature; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.config.GroovyConfigUtils; -import org.jetbrains.plugins.groovy.gpp.GppClosureParameterTypeProvider; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; +import org.jetbrains.plugins.groovy.lang.sam.SamConversionKt; import java.util.Iterator; import java.util.List; @@ -35,10 +37,10 @@ import java.util.List; * @author Max Medvedev */ public class ClosureAsAnonymousParameterEnhancer extends AbstractClosureParameterEnhancer { + @Nullable @Override protected PsiType getClosureParameterType(GrClosableBlock closure, int index) { - List expectedTypes; if (closure.getParent() instanceof GrSafeCastExpression) { @@ -64,16 +66,28 @@ public class ClosureAsAnonymousParameterEnhancer extends AbstractClosureParamete } for (PsiType constraint : expectedTypes) { - final PsiType suggestion = GppClosureParameterTypeProvider.getSingleMethodParameterType(constraint, index, closure); - if (suggestion != null) { - if (GroovyConfigUtils.getInstance().isVersionAtLeast(closure, GroovyConfigUtils.GROOVY2_3)) { - if (suggestion instanceof PsiWildcardType && ((PsiWildcardType)suggestion).isSuper()) { - return ((PsiWildcardType)suggestion).getBound(); - } - } + if (!(constraint instanceof PsiClassType)) continue; - return TypesUtil.substituteAndNormalizeType(suggestion, PsiSubstitutor.EMPTY, null, closure); + PsiClassType.ClassResolveResult result = ((PsiClassType)constraint).resolveGenerics(); + PsiClass resolved = result.getElement(); + if (resolved == null) continue; + + MethodSignature sam = SamConversionKt.findSingleAbstractSignature(resolved); + if (sam == null) continue; + + PsiType[] parameterTypes = sam.getParameterTypes(); + if (index >= parameterTypes.length) continue; + + final PsiType suggestion = result.getSubstitutor().substitute(parameterTypes[index]); + if (suggestion == null) continue; + + if (GroovyConfigUtils.getInstance().isVersionAtLeast(closure, GroovyConfigUtils.GROOVY2_3)) { + if (suggestion instanceof PsiWildcardType && ((PsiWildcardType)suggestion).isSuper()) { + return ((PsiWildcardType)suggestion).getBound(); + } } + + return TypesUtil.substituteAndNormalizeType(suggestion, result.getSubstitutor(), null, closure); } return null; diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java deleted file mode 100644 index 32faeb768041..000000000000 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/typeEnhancers/ClosureToSamConverter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jetbrains.plugins.groovy.lang.psi.typeEnhancers; - -import com.intellij.codeInsight.generation.OverrideImplementExploreUtil; -import com.intellij.openapi.util.Ref; -import com.intellij.psi.*; -import com.intellij.psi.util.MethodSignature; -import com.intellij.psi.util.MethodSignatureUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.groovy.config.GroovyConfigUtils; -import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; -import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature; -import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType; -import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; -import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; -import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; -import org.jetbrains.plugins.groovy.util.LightCacheKey; - -import java.util.Collection; - -/** - * @author Max Medvedev - */ -public class ClosureToSamConverter extends GrTypeConverter { - private static final LightCacheKey> SAM_SIGNATURE_LIGHT_CACHE_KEY = LightCacheKey.createByJavaModificationCount(); - - @Override - public boolean isAllowedInMethodCall() { - return true; - } - - @Override - public Boolean isConvertible(@NotNull PsiType ltype, @NotNull PsiType rtype, @NotNull final GroovyPsiElement context) { - if (rtype instanceof GrClosureType && - ltype instanceof PsiClassType && - isSamConversionAllowed(context) && - !TypesUtil.isClassType(ltype, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { - MethodSignature signature = findSAMSignature(ltype); - if (signature != null) { - final PsiType[] samParameterTypes = signature.getParameterTypes(); - - GrSignature closureSignature = ((GrClosureType)rtype).getSignature(); - - boolean raw = ((PsiClassType)ltype).isRaw(); - if (raw) return true; - - if (GrClosureSignatureUtil.isSignatureApplicable(closureSignature, samParameterTypes, context)) { - return true; - } - } - } - - return null; - } - - public static boolean isSamConversionAllowed(PsiElement context) { - return GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2); - } - - @Nullable - public static MethodSignature findSingleAbstractMethod(@NotNull PsiClass aClass, @NotNull PsiSubstitutor substitutor) { - MethodSignature signature; - Ref cached = SAM_SIGNATURE_LIGHT_CACHE_KEY.getCachedValue(aClass); - if (cached != null) { - signature = cached.get(); - } - else { - Ref newCached = Ref.create(doFindSingleAbstractMethodClass(aClass)); - signature = SAM_SIGNATURE_LIGHT_CACHE_KEY.putCachedValue(aClass, newCached).get(); - } - - return signature != null ? substitute(signature, substitutor): null; - } - - @Nullable - private static MethodSignature doFindSingleAbstractMethodClass(@NotNull PsiClass aClass) { - Collection toImplement = OverrideImplementExploreUtil.getMethodSignaturesToImplement(aClass); - if (toImplement.size() > 1) return null; - - MethodSignature abstractSignature = toImplement.isEmpty() ? null : toImplement.iterator().next(); - for (PsiMethod method : aClass.getMethods()) { - if (method.hasModifierProperty(PsiModifier.ABSTRACT)) { - if (abstractSignature != null) return null; - abstractSignature = method.getSignature(PsiSubstitutor.EMPTY); - } - } - - return abstractSignature; - } - - @NotNull - private static MethodSignature substitute(@NotNull MethodSignature signature, @NotNull PsiSubstitutor substitutor) { - return MethodSignatureUtil.createMethodSignature(signature.getName(), signature.getParameterTypes(), PsiTypeParameter.EMPTY_ARRAY, substitutor, false); - } - - @Nullable - public static MethodSignature findSAMSignature(@Nullable PsiType type) { - if (type instanceof PsiClassType) { - if (TypesUtil.isClassType(type, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) return null; - - PsiClassType.ClassResolveResult result = ((PsiClassType)type).resolveGenerics(); - PsiClass aClass = result.getElement(); - - if (aClass != null) { - return findSingleAbstractMethod(aClass, result.getSubstitutor()); - } - } - - return null; - } -} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java index a5cfc00edac8..b16a7c83200d 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java @@ -20,7 +20,10 @@ import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.openapi.util.VolatileNotNullLazyValue; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; -import com.intellij.psi.util.*; +import com.intellij.psi.PsiClassType.ClassResolveResult; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.TypeConversionUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; @@ -41,7 +44,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMe import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter; import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; -import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureToSamConverter; import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; @@ -50,6 +52,8 @@ import java.util.Collection; import static com.intellij.util.containers.ContainerUtil.emptyList; import static com.intellij.util.containers.ContainerUtil.newHashSet; +import static org.jetbrains.plugins.groovy.lang.sam.SamConversionKt.findSingleAbstractMethod; +import static org.jetbrains.plugins.groovy.lang.sam.SamConversionKt.isSamConversionAllowed; /** * @author Max Medvedev @@ -214,7 +218,7 @@ public class SubstitutorComputer { @Nullable private PsiType handleConversion(@Nullable PsiType paramType, @Nullable PsiType argType) { if (argType instanceof PsiClassType && - ClosureToSamConverter.isSamConversionAllowed(myPlace) && + isSamConversionAllowed(myPlace) && InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE) && !TypesUtil.isClassType(paramType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) { PsiType converted = handleConversionOfSAMType(paramType, (PsiClassType)argType); @@ -235,34 +239,34 @@ public class SubstitutorComputer { } @Nullable - private PsiType handleConversionOfSAMType(@Nullable PsiType samType, @NotNull PsiClassType closure) { - if (samType instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)samType).resolveGenerics(); - PsiClass samClass = resolveResult.getElement(); + private PsiType handleConversionOfSAMType(@Nullable PsiType targetType, @NotNull PsiClassType closure) { + if (!(targetType instanceof PsiClassType)) return null; - if (samClass != null && samClass.getTypeParameters().length != 0) { - MethodSignature samSignature = ClosureToSamConverter.findSingleAbstractMethod(samClass, PsiSubstitutor.EMPTY); - if (samSignature != null) { + ClassResolveResult resolveResult = ((PsiClassType)targetType).resolveGenerics(); - PsiMethod samMethod = MethodSignatureUtil.findMethodBySignature(samClass, samSignature, true); - if (samMethod != null) { - PsiType[] closureArgs = closure.getParameters(); - if (closureArgs.length == 1 && samMethod.getReturnType() != null) { - PsiSubstitutor substitutor = myHelper.inferTypeArguments(samClass.getTypeParameters(), - new PsiType[]{samMethod.getReturnType()}, - closureArgs, - LanguageLevel.JDK_1_7); + PsiClass samClass = resolveResult.getElement(); + if (samClass == null) return null; - if (!substitutor.getSubstitutionMap().isEmpty()) { - return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor); - } - } - } - } - } - } + PsiTypeParameter[] samClassTypeParameters = samClass.getTypeParameters(); + if (samClassTypeParameters.length == 0) return null; - return null; + PsiMethod sam = findSingleAbstractMethod(samClass); + if (sam == null) return null; + + // at this point we know that target type is actually a SAM type + + PsiType samReturnType = sam.getReturnType(); + if (samReturnType == null) return null; + + PsiType[] closureParameters = closure.getParameters(); + if (closureParameters.length != 1) return null; + + PsiSubstitutor substitutor = myHelper.inferTypeArguments( + samClassTypeParameters, new PsiType[]{samReturnType}, closureParameters, LanguageLevel.JDK_1_7 + ); + if (substitutor.getSubstitutionMap().isEmpty()) return null; + + return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt new file mode 100644 index 000000000000..989ff2e158bf --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/ClosureToSamConverter.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.sam + +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiType +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement +import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.ConversionResult +import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrTypeConverter +import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrTypeConverter.ApplicableTo.* +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames.GROOVY_LANG_CLOSURE + +class ClosureToSamConverter : GrTypeConverter() { + + private val myPositions = setOf(ASSIGNMENT, RETURN_VALUE, METHOD_PARAMETER) + + override fun isApplicableTo(position: ApplicableTo) = position in myPositions + + override fun isConvertibleEx(targetType: PsiType, actualType: PsiType, context: GroovyPsiElement, + currentPosition: ApplicableTo): ConversionResult? { + if (targetType !is PsiClassType || actualType !is GrClosureType) return null + if (!isSamConversionAllowed(context)) return null + + val result = targetType.resolveGenerics() + val targetClass = result.element ?: return null + + val targetFqn = targetClass.qualifiedName ?: return null // anonymous classes has no fqn + if (targetFqn == GROOVY_LANG_CLOSURE) return null + + findSingleAbstractSignature(targetClass) ?: return null + return ConversionResult.OK + } +} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt new file mode 100644 index 000000000000..a41b17d66e7a --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/sam/samConversion.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.lang.sam + +import com.intellij.psi.* +import com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.MethodSignature +import org.jetbrains.plugins.groovy.config.GroovyConfigUtils +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod +import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil.isTrait + +fun findSingleAbstractMethod(clazz: PsiClass): PsiMethod? = findSingleAbstractSignatureCached(clazz)?.method + +fun findSingleAbstractSignature(clazz: PsiClass): MethodSignature? = findSingleAbstractSignatureCached(clazz) + +private fun findSingleAbstractSignatureCached(clazz: PsiClass): HierarchicalMethodSignature? { + return CachedValuesManager.getCachedValue(clazz) { + CachedValueProvider.Result.create(doFindSingleAbstractSignature(clazz), clazz) + } +} + +private fun doFindSingleAbstractSignature(clazz: PsiClass): HierarchicalMethodSignature? { + var result: HierarchicalMethodSignature? = null + for (signature in clazz.visibleSignatures) { + if (!isEffectivelyAbstractMethod(signature)) continue + if (result != null) return null // found another abstract method + result = signature + } + return result +} + +private fun isEffectivelyAbstractMethod(signature: HierarchicalMethodSignature): Boolean { + val method = signature.method + if (!method.hasModifierProperty(PsiModifier.ABSTRACT)) return false + if (isObjectMethod(signature)) return false + if (isImplementedTraitMethod(method)) return false + return true +} + +private fun isObjectMethod(signature: HierarchicalMethodSignature): Boolean { + return signature.superSignatures.any { + it.method.containingClass?.qualifiedName == JAVA_LANG_OBJECT + } +} + +private fun isImplementedTraitMethod(method: PsiMethod): Boolean { + val clazz = method.containingClass ?: return false + if (!isTrait(clazz)) return false + val traitMethod = method as? GrMethod ?: return false + return traitMethod.block != null +} + +fun isSamConversionAllowed(context: PsiElement): Boolean { + return GroovyConfigUtils.getInstance().isVersionAtLeast(context, GroovyConfigUtils.GROOVY2_2) +} diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index 592baa420813..78350f242cad 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -235,7 +235,7 @@ - + diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy index fd9b898a71f5..aa80941498d5 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrAssignabilityClosureToSamTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ interface B {} A a = {print 1} A a1 = {->print 1} -A a2 = {String s->print 1} +A a2 = {String s->print 1} B b = {print 2} ''') } @@ -66,7 +66,7 @@ interface A { A a1 = {print 1} A a2 = {String s -> print 1} -A a3 = {int s -> print 1} +A a3 = {int s -> print 1} A a4 = {int s -> print 1} A a7 = {int s, String y -> print 1} A a5 = { print 1} @@ -84,7 +84,7 @@ interface B extends A {} B b1 = {print 1} B b2 = {String s -> print 1} -B b3 = {int s -> print 1} +B b3 = {int s -> print 1} ''') } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy index 8226aabf53d6..86b5c798a61e 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/Groovy23HighlightingTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,7 +52,7 @@ public void exec(T t, Action f, X x) { def foo() { exec('foo', { String t, Integer x -> ; }, 1) - exec('foo', { Integer t, Integer x -> ; }, 1) + exec('foo', { Integer t, Integer x -> ; }, 1) } ''') } @@ -85,7 +85,7 @@ public void exec(T t, Action f, X x) { def foo() { exec('foo', { String s, Integer x -> print s + x }, 1) - exec('foo', { Integer s, Integer x -> print 9 }, 1) + exec('foo', { Integer s, Integer x -> print 9 }, 1) } ''') }