• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.writing;
18 
19 import static androidx.room.compiler.processing.XElementKt.isConstructor;
20 import static androidx.room.compiler.processing.XElementKt.isMethod;
21 import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
22 import static androidx.room.compiler.processing.XTypeKt.isVoid;
23 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
24 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
25 import static com.google.common.base.Preconditions.checkArgument;
26 import static com.squareup.javapoet.MethodSpec.methodBuilder;
27 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter;
28 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
29 import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable;
30 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
31 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
32 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
33 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
34 import static dagger.internal.codegen.javapoet.CodeBlocks.toConcatenatedCodeBlock;
35 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
36 import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName;
37 import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
38 import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible;
39 import static dagger.internal.codegen.langmodel.Accessibility.isRawTypePubliclyAccessible;
40 import static dagger.internal.codegen.xprocessing.XElements.asConstructor;
41 import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
42 import static dagger.internal.codegen.xprocessing.XElements.asField;
43 import static dagger.internal.codegen.xprocessing.XElements.asMethod;
44 import static dagger.internal.codegen.xprocessing.XElements.asMethodParameter;
45 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
46 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
47 import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames;
48 import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName;
49 import static javax.lang.model.element.Modifier.PUBLIC;
50 import static javax.lang.model.element.Modifier.STATIC;
51 
52 import androidx.room.compiler.processing.XAnnotation;
53 import androidx.room.compiler.processing.XConstructorElement;
54 import androidx.room.compiler.processing.XExecutableElement;
55 import androidx.room.compiler.processing.XExecutableParameterElement;
56 import androidx.room.compiler.processing.XFieldElement;
57 import androidx.room.compiler.processing.XMethodElement;
58 import androidx.room.compiler.processing.XType;
59 import androidx.room.compiler.processing.XTypeElement;
60 import androidx.room.compiler.processing.XVariableElement;
61 import com.google.common.collect.ImmutableList;
62 import com.google.common.collect.ImmutableMap;
63 import com.google.common.collect.ImmutableSet;
64 import com.squareup.javapoet.AnnotationSpec;
65 import com.squareup.javapoet.ClassName;
66 import com.squareup.javapoet.CodeBlock;
67 import com.squareup.javapoet.MethodSpec;
68 import com.squareup.javapoet.ParameterSpec;
69 import com.squareup.javapoet.TypeName;
70 import dagger.internal.Preconditions;
71 import dagger.internal.codegen.base.UniqueNameSet;
72 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
73 import dagger.internal.codegen.binding.Nullability;
74 import dagger.internal.codegen.binding.ProvisionBinding;
75 import dagger.internal.codegen.compileroption.CompilerOptions;
76 import dagger.internal.codegen.extension.DaggerCollectors;
77 import dagger.internal.codegen.javapoet.TypeNames;
78 import dagger.internal.codegen.model.DaggerAnnotation;
79 import dagger.internal.codegen.model.DependencyRequest;
80 import dagger.internal.codegen.xprocessing.XAnnotations;
81 import java.util.List;
82 import java.util.Optional;
83 import java.util.function.Function;
84 
85 /** Convenience methods for creating and invoking {@link InjectionMethod}s. */
86 final class InjectionMethods {
87 
88   /**
89    * A method that returns an object from a {@code @Provides} method or an {@code @Inject}ed
90    * constructor. Its parameters match the dependency requests for constructor and members
91    * injection.
92    *
93    * <p>For {@code @Provides} methods named "foo", the method name is "proxyFoo". For example:
94    *
95    * <pre><code>
96    * abstract class FooModule {
97    *   {@literal @Provides} static Foo provideFoo(Bar bar, Baz baz) { … }
98    * }
99    *
100    * public static proxyProvideFoo(Bar bar, Baz baz) { … }
101    * </code></pre>
102    *
103    * <p>For {@code @Inject}ed constructors, the method name is "newFoo". For example:
104    *
105    * <pre><code>
106    * class Foo {
107    *   {@literal @Inject} Foo(Bar bar) {}
108    * }
109    *
110    * public static Foo newFoo(Bar bar) { … }
111    * </code></pre>
112    */
113   static final class ProvisionMethod {
114     // These names are already defined in factories and shouldn't be used for the proxy method name.
115     private static final ImmutableSet<String> BANNED_PROXY_NAMES = ImmutableSet.of("get", "create");
116 
117     /**
118      * Returns a method that invokes the binding's {@linkplain ProvisionBinding#bindingElement()
119      * constructor} and injects the instance's members.
120      */
create(ProvisionBinding binding, CompilerOptions compilerOptions)121     static MethodSpec create(ProvisionBinding binding, CompilerOptions compilerOptions) {
122       XExecutableElement executableElement = asExecutable(binding.bindingElement().get());
123       if (isConstructor(executableElement)) {
124         return constructorProxy(asConstructor(executableElement));
125       } else if (isMethod(executableElement)) {
126         XMethodElement method = asMethod(executableElement);
127         String methodName =
128             BANNED_PROXY_NAMES.contains(getSimpleName(method))
129                 ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(method))
130                 : getSimpleName(method);
131         return methodProxy(
132             method,
133             methodName,
134             InstanceCastPolicy.IGNORE,
135             CheckNotNullPolicy.get(binding, compilerOptions));
136       }
137       throw new AssertionError(executableElement);
138     }
139 
140     /**
141      * Invokes the injection method for {@code binding}, with the dependencies transformed with the
142      * {@code dependencyUsage} function.
143      */
invoke( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, Function<XExecutableParameterElement, String> uniqueAssistedParameterName, ClassName requestingClass, Optional<CodeBlock> moduleReference, CompilerOptions compilerOptions)144     static CodeBlock invoke(
145         ProvisionBinding binding,
146         Function<DependencyRequest, CodeBlock> dependencyUsage,
147         Function<XExecutableParameterElement, String> uniqueAssistedParameterName,
148         ClassName requestingClass,
149         Optional<CodeBlock> moduleReference,
150         CompilerOptions compilerOptions) {
151       ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
152       moduleReference.ifPresent(arguments::add);
153       invokeArguments(binding, dependencyUsage, uniqueAssistedParameterName)
154           .forEach(arguments::add);
155 
156       ClassName enclosingClass = generatedClassNameForBinding(binding);
157       MethodSpec methodSpec = create(binding, compilerOptions);
158       return invokeMethod(methodSpec, arguments.build(), enclosingClass, requestingClass);
159     }
160 
invokeArguments( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, Function<XExecutableParameterElement, String> uniqueAssistedParameterName)161     static ImmutableList<CodeBlock> invokeArguments(
162         ProvisionBinding binding,
163         Function<DependencyRequest, CodeBlock> dependencyUsage,
164         Function<XExecutableParameterElement, String> uniqueAssistedParameterName) {
165       ImmutableMap<XExecutableParameterElement, DependencyRequest> dependencyRequestMap =
166           binding.provisionDependencies().stream()
167               .collect(
168                   toImmutableMap(
169                       request -> asMethodParameter(request.requestElement().get().xprocessing()),
170                       request -> request));
171 
172       ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
173       XExecutableElement method = asExecutable(binding.bindingElement().get());
174       for (XExecutableParameterElement parameter : method.getParameters()) {
175         if (isAssistedParameter(parameter)) {
176           arguments.add(CodeBlock.of("$L", uniqueAssistedParameterName.apply(parameter)));
177         } else if (dependencyRequestMap.containsKey(parameter)) {
178           DependencyRequest request = dependencyRequestMap.get(parameter);
179           arguments.add(dependencyUsage.apply(request));
180         } else {
181           throw new AssertionError("Unexpected parameter: " + parameter);
182         }
183       }
184 
185       return arguments.build();
186     }
187 
constructorProxy(XConstructorElement constructor)188     private static MethodSpec constructorProxy(XConstructorElement constructor) {
189       XTypeElement enclosingType = constructor.getEnclosingElement();
190       MethodSpec.Builder builder =
191           methodBuilder("newInstance")
192               .addModifiers(PUBLIC, STATIC)
193               .varargs(constructor.isVarArgs())
194               .returns(enclosingType.getType().getTypeName())
195               .addTypeVariables(typeVariableNames(enclosingType));
196 
197       copyThrows(builder, constructor);
198 
199       CodeBlock arguments =
200           copyParameters(builder, new UniqueNameSet(), constructor.getParameters());
201       return builder
202           .addStatement("return new $T($L)", enclosingType.getType().getTypeName(), arguments)
203           .build();
204     }
205 
206     /**
207      * Returns {@code true} if injecting an instance of {@code binding} from {@code callingPackage}
208      * requires the use of an injection method.
209      */
requiresInjectionMethod( ProvisionBinding binding, CompilerOptions compilerOptions, ClassName requestingClass)210     static boolean requiresInjectionMethod(
211         ProvisionBinding binding, CompilerOptions compilerOptions, ClassName requestingClass) {
212       XExecutableElement executableElement = asExecutable(binding.bindingElement().get());
213       return !binding.injectionSites().isEmpty()
214           || binding.shouldCheckForNull(compilerOptions)
215           || !isElementAccessibleFrom(executableElement, requestingClass.packageName())
216           // This check should be removable once we drop support for -source 7
217           || executableElement.getParameters().stream()
218               .map(XExecutableParameterElement::getType)
219               .anyMatch(type -> !isRawTypeAccessible(type, requestingClass.packageName()));
220     }
221   }
222 
223   /**
224    * A static method that injects one member of an instance of a type. Its first parameter is an
225    * instance of the type to be injected. The remaining parameters match the dependency requests for
226    * the injection site.
227    *
228    * <p>Example:
229    *
230    * <pre><code>
231    * class Foo {
232    *   {@literal @Inject} Bar bar;
233    *   {@literal @Inject} void setThings(Baz baz, Qux qux) {}
234    * }
235    *
236    * public static injectBar(Foo instance, Bar bar) { … }
237    * public static injectSetThings(Foo instance, Baz baz, Qux qux) { … }
238    * </code></pre>
239    */
240   static final class InjectionSiteMethod {
241     /**
242      * When a type has an inaccessible member from a supertype (e.g. an @Inject field in a parent
243      * that's in a different package), a method in the supertype's package must be generated to give
244      * the subclass's members injector a way to inject it. Each potentially inaccessible member
245      * receives its own method, as the subclass may need to inject them in a different order from
246      * the parent class.
247      */
create(InjectionSite injectionSite)248     static MethodSpec create(InjectionSite injectionSite) {
249       String methodName = methodName(injectionSite);
250       switch (injectionSite.kind()) {
251         case METHOD:
252           return methodProxy(
253               asMethod(injectionSite.element()),
254               methodName,
255               InstanceCastPolicy.CAST_IF_NOT_PUBLIC,
256               CheckNotNullPolicy.IGNORE);
257         case FIELD:
258           Optional<XAnnotation> qualifier =
259               injectionSite.dependencies().stream()
260                   // methods for fields have a single dependency request
261                   .collect(DaggerCollectors.onlyElement())
262                   .key()
263                   .qualifier()
264                   .map(DaggerAnnotation::xprocessing);
265           return fieldProxy(asField(injectionSite.element()), methodName, qualifier);
266       }
267       throw new AssertionError(injectionSite);
268     }
269 
270     /**
271      * Invokes each of the injection methods for {@code injectionSites}, with the dependencies
272      * transformed using the {@code dependencyUsage} function.
273      *
274      * @param instanceType the type of the {@code instance} parameter
275      */
invokeAll( ImmutableSet<InjectionSite> injectionSites, ClassName generatedTypeName, CodeBlock instanceCodeBlock, XType instanceType, Function<DependencyRequest, CodeBlock> dependencyUsage)276     static CodeBlock invokeAll(
277         ImmutableSet<InjectionSite> injectionSites,
278         ClassName generatedTypeName,
279         CodeBlock instanceCodeBlock,
280         XType instanceType,
281         Function<DependencyRequest, CodeBlock> dependencyUsage) {
282       return injectionSites.stream()
283           .map(
284               injectionSite -> {
285                 XType injectSiteType = injectionSite.enclosingTypeElement().getType();
286 
287                 // If instance has been declared as Object because it is not accessible from the
288                 // component, but the injectionSite is in a supertype of instanceType that is
289                 // publicly accessible, the InjectionSiteMethod will request the actual type and not
290                 // Object as the first parameter. If so, cast to the supertype which is accessible
291                 // from within generatedTypeName
292                 CodeBlock maybeCastedInstance =
293                     instanceType.getTypeName().equals(TypeName.OBJECT)
294                             && isRawTypeAccessible(injectSiteType, generatedTypeName.packageName())
295                         ? CodeBlock.of("($T) $L", erasedTypeName(injectSiteType), instanceCodeBlock)
296                         : instanceCodeBlock;
297                 return CodeBlock.of(
298                     "$L;",
299                     invoke(injectionSite, generatedTypeName, maybeCastedInstance, dependencyUsage));
300               })
301           .collect(toConcatenatedCodeBlock());
302     }
303 
304     /**
305      * Invokes the injection method for {@code injectionSite}, with the dependencies transformed
306      * using the {@code dependencyUsage} function.
307      */
invoke( InjectionSite injectionSite, ClassName generatedTypeName, CodeBlock instanceCodeBlock, Function<DependencyRequest, CodeBlock> dependencyUsage)308     private static CodeBlock invoke(
309         InjectionSite injectionSite,
310         ClassName generatedTypeName,
311         CodeBlock instanceCodeBlock,
312         Function<DependencyRequest, CodeBlock> dependencyUsage) {
313       ImmutableList<CodeBlock> arguments =
314           ImmutableList.<CodeBlock>builder()
315               .add(instanceCodeBlock)
316               .addAll(
317                   injectionSite.dependencies().stream()
318                       .map(dependencyUsage)
319                       .collect(toImmutableList()))
320               .build();
321       ClassName enclosingClass = membersInjectorNameForType(injectionSite.enclosingTypeElement());
322       MethodSpec methodSpec = create(injectionSite);
323       return invokeMethod(methodSpec, arguments, enclosingClass, generatedTypeName);
324     }
325 
326     /*
327      * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples:
328      *
329      *  - @Inject void members() {} will generate a method that conflicts with the instance
330      *    method `injectMembers(T)`
331      *  - Adding the index could conflict with another member:
332      *      @Inject void a(Object o) {}
333      *      @Inject void a(String s) {}
334      *      @Inject void a1(String s) {}
335      *
336      *    Here, Method a(String) will add the suffix "1", which will conflict with the method
337      *    generated for a1(String)
338      *  - Members named "members" or "methods" could also conflict with the {@code static} injection
339      *    method.
340      */
methodName(InjectionSite injectionSite)341     private static String methodName(InjectionSite injectionSite) {
342       int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName();
343       String indexString = index == 0 ? "" : String.valueOf(index + 1);
344       return "inject"
345           + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element()))
346           + indexString;
347     }
348   }
349 
350   private enum InstanceCastPolicy {
351     CAST_IF_NOT_PUBLIC, IGNORE;
352 
useObjectType(XType instanceType)353     boolean useObjectType(XType instanceType) {
354       return this == CAST_IF_NOT_PUBLIC && !isRawTypePubliclyAccessible(instanceType);
355     }
356   }
357 
358   private enum CheckNotNullPolicy {
359     IGNORE, CHECK_FOR_NULL;
360 
checkForNull(CodeBlock maybeNull)361     CodeBlock checkForNull(CodeBlock maybeNull) {
362       return this.equals(IGNORE)
363           ? maybeNull
364           : CodeBlock.of("$T.checkNotNullFromProvides($L)", Preconditions.class, maybeNull);
365     }
366 
get(ProvisionBinding binding, CompilerOptions compilerOptions)367     static CheckNotNullPolicy get(ProvisionBinding binding, CompilerOptions compilerOptions) {
368       return binding.shouldCheckForNull(compilerOptions) ? CHECK_FOR_NULL : IGNORE;
369     }
370   }
371 
methodProxy( XMethodElement method, String methodName, InstanceCastPolicy instanceCastPolicy, CheckNotNullPolicy checkNotNullPolicy)372   private static MethodSpec methodProxy(
373       XMethodElement method,
374       String methodName,
375       InstanceCastPolicy instanceCastPolicy,
376       CheckNotNullPolicy checkNotNullPolicy) {
377     XTypeElement enclosingType = asTypeElement(method.getEnclosingElement());
378 
379     MethodSpec.Builder builder =
380         methodBuilder(methodName)
381             .addModifiers(PUBLIC, STATIC)
382             .varargs(method.isVarArgs())
383             .addTypeVariables(method.getExecutableType().getTypeVariableNames());
384 
385     UniqueNameSet parameterNameSet = new UniqueNameSet();
386     CodeBlock instance;
387     if (method.isStatic() || enclosingType.isCompanionObject()) {
388       instance = CodeBlock.of("$T", rawTypeName(enclosingType.getType().getTypeName()));
389     } else if (enclosingType.isKotlinObject()) {
390       // Call through the singleton instance.
391       // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods
392       instance = CodeBlock.of("$T.INSTANCE", rawTypeName(enclosingType.getType().getTypeName()));
393     } else {
394       builder.addTypeVariables(typeVariableNames(enclosingType));
395       boolean useObject = instanceCastPolicy.useObjectType(enclosingType.getType());
396       instance = copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject);
397     }
398     CodeBlock arguments = copyParameters(builder, parameterNameSet, method.getParameters());
399     CodeBlock invocation =
400         checkNotNullPolicy.checkForNull(
401             CodeBlock.of("$L.$L($L)", instance, method.getJvmName(), arguments));
402 
403     copyThrows(builder, method);
404 
405     if (isVoid(method.getReturnType())) {
406       return builder.addStatement("$L", invocation).build();
407     } else {
408       Nullability nullability = Nullability.of(method);
409       nullability
410           .nullableAnnotations()
411           .forEach(builder::addAnnotation);
412       return builder
413           .returns(method.getReturnType().getTypeName())
414           .addStatement("return $L", invocation)
415           .build();
416     }
417   }
418 
fieldProxy( XFieldElement field, String methodName, Optional<XAnnotation> qualifier)419   private static MethodSpec fieldProxy(
420       XFieldElement field, String methodName, Optional<XAnnotation> qualifier) {
421     XTypeElement enclosingType = asTypeElement(field.getEnclosingElement());
422 
423     MethodSpec.Builder builder =
424         methodBuilder(methodName)
425             .addModifiers(PUBLIC, STATIC)
426             .addAnnotation(
427                 AnnotationSpec.builder(TypeNames.INJECTED_FIELD_SIGNATURE)
428                     .addMember("value", "$S", memberInjectedFieldSignatureForVariable(field))
429                     .build())
430             .addTypeVariables(typeVariableNames(enclosingType));
431 
432     qualifier.map(XAnnotations::getAnnotationSpec).ifPresent(builder::addAnnotation);
433 
434     boolean useObject = !isRawTypePubliclyAccessible(enclosingType.getType());
435     UniqueNameSet parameterNameSet = new UniqueNameSet();
436     CodeBlock instance =
437         copyInstance(builder, parameterNameSet, enclosingType.getType(), useObject);
438     CodeBlock argument = copyParameters(builder, parameterNameSet, ImmutableList.of(field));
439     return builder.addStatement("$L.$L = $L", instance, getSimpleName(field), argument).build();
440   }
441 
invokeMethod( MethodSpec methodSpec, ImmutableList<CodeBlock> parameters, ClassName enclosingClass, ClassName requestingClass)442   private static CodeBlock invokeMethod(
443       MethodSpec methodSpec,
444       ImmutableList<CodeBlock> parameters,
445       ClassName enclosingClass,
446       ClassName requestingClass) {
447     checkArgument(methodSpec.parameters.size() == parameters.size());
448     CodeBlock parameterBlock = makeParametersCodeBlock(parameters);
449     return enclosingClass.equals(requestingClass)
450         ? CodeBlock.of("$L($L)", methodSpec.name, parameterBlock)
451         : CodeBlock.of("$T.$L($L)", enclosingClass, methodSpec.name, parameterBlock);
452   }
453 
copyThrows(MethodSpec.Builder methodBuilder, XExecutableElement method)454   private static void copyThrows(MethodSpec.Builder methodBuilder, XExecutableElement method) {
455     method.getThrownTypes().stream().map(XType::getTypeName).forEach(methodBuilder::addException);
456   }
457 
copyParameters( MethodSpec.Builder methodBuilder, UniqueNameSet parameterNameSet, List<? extends XVariableElement> parameters)458   private static CodeBlock copyParameters(
459       MethodSpec.Builder methodBuilder,
460       UniqueNameSet parameterNameSet,
461       List<? extends XVariableElement> parameters) {
462     return parameters.stream()
463         .map(
464             parameter -> {
465               String name =
466                   parameterNameSet.getUniqueName(
467                       isMethodParameter(parameter)
468                           ? asMethodParameter(parameter).getJvmName()
469                           : getSimpleName(parameter));
470               boolean useObject = !isRawTypePubliclyAccessible(parameter.getType());
471               return copyParameter(methodBuilder, parameter.getType(), name, useObject);
472             })
473         .collect(toParametersCodeBlock());
474   }
475 
476   private static CodeBlock copyParameter(
477       MethodSpec.Builder methodBuilder, XType type, String name, boolean useObject) {
478     TypeName typeName = useObject ? TypeName.OBJECT : type.getTypeName();
479     methodBuilder.addParameter(ParameterSpec.builder(typeName, name).build());
480     return useObject ? CodeBlock.of("($T) $L", type.getTypeName(), name) : CodeBlock.of("$L", name);
481   }
482 
483   private static CodeBlock copyInstance(
484       MethodSpec.Builder methodBuilder,
485       UniqueNameSet parameterNameSet,
486       XType type,
487       boolean useObject) {
488     CodeBlock instance =
489         copyParameter(methodBuilder, type, parameterNameSet.getUniqueName("instance"), useObject);
490     // If we had to cast the instance add an extra parenthesis incase we're calling a method on it.
491     return useObject ? CodeBlock.of("($L)", instance) : instance;
492   }
493 }
494