• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2012 Google LLC
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 package com.google.auto.value.processor;
17 
18 import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
19 import static com.google.auto.common.MoreStreams.toImmutableList;
20 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
21 import static com.google.common.base.Preconditions.checkState;
22 import static com.google.common.collect.Sets.difference;
23 import static com.google.common.collect.Sets.intersection;
24 import static java.util.Comparator.naturalOrder;
25 import static java.util.stream.Collectors.joining;
26 
27 import com.google.auto.service.AutoService;
28 import com.google.auto.value.extension.AutoValueExtension;
29 import com.google.common.annotations.VisibleForTesting;
30 import com.google.common.base.Strings;
31 import com.google.common.base.Throwables;
32 import com.google.common.collect.ImmutableBiMap;
33 import com.google.common.collect.ImmutableList;
34 import com.google.common.collect.ImmutableListMultimap;
35 import com.google.common.collect.ImmutableMap;
36 import com.google.common.collect.ImmutableSet;
37 import java.lang.annotation.Annotation;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Optional;
43 import java.util.ServiceConfigurationError;
44 import java.util.Set;
45 import javax.annotation.processing.ProcessingEnvironment;
46 import javax.annotation.processing.Processor;
47 import javax.annotation.processing.SupportedAnnotationTypes;
48 import javax.lang.model.element.AnnotationMirror;
49 import javax.lang.model.element.ExecutableElement;
50 import javax.lang.model.element.TypeElement;
51 import javax.lang.model.type.TypeKind;
52 import javax.lang.model.type.TypeMirror;
53 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
54 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
55 
56 /**
57  * Javac annotation processor (compiler plugin) for value types; user code never references this
58  * class.
59  *
60  * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
61  * @author Éamonn McManus
62  */
63 @AutoService(Processor.class)
64 @SupportedAnnotationTypes(AUTO_VALUE_NAME)
65 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
66 public class AutoValueProcessor extends AutoValueishProcessor {
67   static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers";
68 
69   // We moved MemoizeExtension to a different package, which had an unexpected effect:
70   // now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the
71   // old and the new versions of MemoizeExtension. So we exclude the old version if we see it.
72   // The new version will be bundled with this processor so we should always find it.
73   private static final String OLD_MEMOIZE_EXTENSION =
74       "com.google.auto.value.extension.memoized.MemoizeExtension";
75 
AutoValueProcessor()76   public AutoValueProcessor() {
77     this(AutoValueProcessor.class.getClassLoader());
78   }
79 
80   @VisibleForTesting
AutoValueProcessor(ClassLoader loaderForExtensions)81   AutoValueProcessor(ClassLoader loaderForExtensions) {
82     this(ImmutableList.of(), loaderForExtensions);
83   }
84 
85   @VisibleForTesting
AutoValueProcessor(Iterable<? extends AutoValueExtension> testExtensions)86   public AutoValueProcessor(Iterable<? extends AutoValueExtension> testExtensions) {
87     this(testExtensions, null);
88   }
89 
AutoValueProcessor( Iterable<? extends AutoValueExtension> testExtensions, ClassLoader loaderForExtensions)90   private AutoValueProcessor(
91       Iterable<? extends AutoValueExtension> testExtensions, ClassLoader loaderForExtensions) {
92     super(AUTO_VALUE_NAME, /* appliesToInterfaces= */ false);
93     this.extensions = ImmutableList.copyOf(testExtensions);
94     this.loaderForExtensions = loaderForExtensions;
95   }
96 
97   // Depending on how this AutoValueProcessor was constructed, we might already have a list of
98   // extensions when init() is run, or, if `loaderForExtensions` is not null, it is a ClassLoader
99   // that will be used to get the list using the ServiceLoader API.
100   private ImmutableList<AutoValueExtension> extensions;
101   private final ClassLoader loaderForExtensions;
102 
103   @VisibleForTesting
extensionsFromLoader(ClassLoader loader)104   static ImmutableList<AutoValueExtension> extensionsFromLoader(ClassLoader loader) {
105     return SimpleServiceLoader.load(AutoValueExtension.class, loader).stream()
106         .filter(ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION))
107         .collect(toImmutableList());
108   }
109 
110   @Override
init(ProcessingEnvironment processingEnv)111   public synchronized void init(ProcessingEnvironment processingEnv) {
112     super.init(processingEnv);
113 
114     if (loaderForExtensions != null) {
115       checkState(extensions.isEmpty());
116       try {
117         extensions = extensionsFromLoader(loaderForExtensions);
118       } catch (RuntimeException | Error e) {
119         String explain =
120             (e instanceof ServiceConfigurationError)
121                 ? " This may be due to a corrupt jar file in the compiler's classpath."
122                 : "";
123         errorReporter()
124             .reportWarning(
125                 null,
126                 "[AutoValueExtensionsException] An exception occurred while looking for AutoValue"
127                     + " extensions. No extensions will function.%s\n%s",
128                 explain,
129                 Throwables.getStackTraceAsString(e));
130         extensions = ImmutableList.of();
131       }
132     }
133   }
134 
135   @Override
getSupportedOptions()136   public ImmutableSet<String> getSupportedOptions() {
137     ImmutableSet.Builder<String> builder = ImmutableSet.builder();
138     AutoValueExtension.IncrementalExtensionType incrementalType =
139         extensions.stream()
140             .map(e -> e.incrementalType(processingEnv))
141             .min(naturalOrder())
142             .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING);
143     builder
144         .add(OMIT_IDENTIFIERS_OPTION)
145         .add(Nullables.NULLABLE_OPTION)
146         .addAll(optionsFor(incrementalType));
147     for (AutoValueExtension extension : extensions) {
148       builder.addAll(extension.getSupportedOptions());
149     }
150     return builder.build();
151   }
152 
optionsFor( AutoValueExtension.IncrementalExtensionType incrementalType)153   private static ImmutableSet<String> optionsFor(
154       AutoValueExtension.IncrementalExtensionType incrementalType) {
155     switch (incrementalType) {
156       case ISOLATING:
157         return ImmutableSet.of(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
158       case AGGREGATING:
159         return ImmutableSet.of(IncrementalAnnotationProcessorType.AGGREGATING.getProcessorOption());
160       case UNKNOWN:
161         return ImmutableSet.of();
162     }
163     throw new AssertionError(incrementalType);
164   }
165 
generatedSubclassName(TypeElement type, int depth)166   static String generatedSubclassName(TypeElement type, int depth) {
167     return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_");
168   }
169 
170   @Override
processType(TypeElement type)171   void processType(TypeElement type) {
172     if (ancestorIsAutoValue(type)) {
173       errorReporter()
174           .abortWithError(type, "[AutoValueExtend] One @AutoValue class may not extend another");
175     }
176     if (implementsAnnotation(type)) {
177       errorReporter()
178           .abortWithError(
179               type,
180               "[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation"
181                   + " interface; try using @AutoAnnotation instead");
182     }
183 
184     // We are going to classify the methods of the @AutoValue class into several categories.
185     // This covers the methods in the class itself and the ones it inherits from supertypes.
186     // First, the only concrete (non-abstract) methods we are interested in are overrides of
187     // Object methods (equals, hashCode, toString), which signal that we should not generate
188     // an implementation of those methods.
189     // Then, each abstract method is one of the following:
190     // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
191     // (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for
192     //     this @AutoValue class.
193     // (3) An abstract method that will be consumed by an extension, such as
194     //     Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int).
195     // The describeContents() example shows a quirk here: initially we will identify it as a
196     // property, which means that we need to reconstruct the list of properties after allowing
197     // extensions to consume abstract methods.
198     // If there are abstract methods that don't fit any of the categories above, that is an error
199     // which we signal explicitly to avoid confusion.
200 
201     ImmutableSet<ExecutableElement> methods =
202         getLocalAndInheritedMethods(
203             type, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
204     ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods);
205 
206     BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter());
207     Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
208     ImmutableSet<ExecutableElement> toBuilderMethods;
209     if (builder.isPresent()) {
210       toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), type, abstractMethods);
211     } else {
212       toBuilderMethods = ImmutableSet.of();
213     }
214 
215     ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes =
216         propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
217     ImmutableMap<String, ExecutableElement> properties =
218         propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
219 
220     ExtensionContext context =
221         new ExtensionContext(
222             processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods);
223     ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context);
224     ImmutableSet<ExecutableElement> consumedMethods =
225         methodsConsumedByExtensions(
226             type, applicableExtensions, context, abstractMethods, properties);
227 
228     if (!consumedMethods.isEmpty()) {
229       ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods;
230       abstractMethods = immutableSetDifference(abstractMethods, consumedMethods);
231       toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods);
232       propertyMethodsAndTypes =
233           propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
234       properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
235       context =
236           new ExtensionContext(
237               processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods);
238     }
239 
240     ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
241     boolean extensionsPresent = !applicableExtensions.isEmpty();
242     validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent);
243 
244     String finalSubclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type, 0));
245     AutoValueTemplateVars vars = new AutoValueTemplateVars();
246     vars.types = processingEnv.getTypeUtils();
247     vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
248     defineSharedVarsForType(type, methods, vars);
249     defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder);
250     vars.builtType = vars.origClass + vars.actualTypes;
251     vars.build = "new " + finalSubclass + vars.actualTypes;
252 
253     // If we've encountered problems then we might end up invoking extensions with inconsistent
254     // state. Anyway we probably don't want to generate code which is likely to provoke further
255     // compile errors to add to the ones we've already seen.
256     errorReporter().abortIfAnyError();
257 
258     GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
259     vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();
260 
261     builder.ifPresent(context::setBuilderContext);
262     int subclassDepth = writeExtensions(type, context, applicableExtensions);
263     String subclass = generatedSubclassName(type, subclassDepth);
264     vars.subclass = TypeSimplifier.simpleNameOf(subclass);
265     vars.isFinal = (subclassDepth == 0);
266     vars.modifiers = vars.isFinal ? "final " : "abstract ";
267 
268     String text = vars.toText();
269     text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
270     text = Reformatter.fixup(text);
271     writeSourceFile(subclass, text, type);
272     GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
273     gwtSerialization.maybeWriteGwtSerializer(vars, finalSubclass);
274   }
275 
276   // Invokes each of the given extensions to generate its subclass, and returns the number of
277   // hierarchy classes that extensions generated. This number is then the number of $ characters
278   // that should precede the name of the AutoValue implementation class.
279   // Assume the @AutoValue class is com.example.Foo.Bar. Then if there are no
280   // extensions the returned value will be 0, so the AutoValue implementation will be
281   // com.example.AutoValue_Foo_Bar. If there is one extension, it will be asked to
282   // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar. If it does so (returns
283   // non-null) then the returned value will be 1, so the AutoValue implementation will be
284   // com.example.$AutoValue_Foo_Bar. Otherwise, the returned value will still be 0. Likewise,
285   // if there is a second extension and both extensions return non-null, the first one will
286   // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar, the second will generate
287   // $AutoValue_Foo_Bar with parent $$AutoValue_Foo_Bar, and the returned value will be 2 for
288   // com.example.$$AutoValue_Foo_Bar.
writeExtensions( TypeElement type, ExtensionContext context, ImmutableList<AutoValueExtension> applicableExtensions)289   private int writeExtensions(
290       TypeElement type,
291       ExtensionContext context,
292       ImmutableList<AutoValueExtension> applicableExtensions) {
293     int writtenSoFar = 0;
294     for (AutoValueExtension extension : applicableExtensions) {
295       String parentFqName = generatedSubclassName(type, writtenSoFar + 1);
296       String parentSimpleName = TypeSimplifier.simpleNameOf(parentFqName);
297       String classFqName = generatedSubclassName(type, writtenSoFar);
298       String classSimpleName = TypeSimplifier.simpleNameOf(classFqName);
299       boolean isFinal = (writtenSoFar == 0);
300       String source = extension.generateClass(context, classSimpleName, parentSimpleName, isFinal);
301       if (source != null) {
302         source = Reformatter.fixup(source);
303         writeSourceFile(classFqName, source, type);
304         writtenSoFar++;
305       }
306     }
307     return writtenSoFar;
308   }
309 
applicableExtensions( TypeElement type, ExtensionContext context)310   private ImmutableList<AutoValueExtension> applicableExtensions(
311       TypeElement type, ExtensionContext context) {
312     List<AutoValueExtension> applicableExtensions = new ArrayList<>();
313     List<AutoValueExtension> finalExtensions = new ArrayList<>();
314     for (AutoValueExtension extension : extensions) {
315       if (extension.applicable(context)) {
316         if (extension.mustBeFinal(context)) {
317           finalExtensions.add(extension);
318         } else {
319           applicableExtensions.add(extension);
320         }
321       }
322     }
323     switch (finalExtensions.size()) {
324       case 0:
325         break;
326       case 1:
327         applicableExtensions.add(0, finalExtensions.get(0));
328         break;
329       default:
330         errorReporter()
331             .reportError(
332                 type,
333                 "[AutoValueMultiFinal] More than one extension wants to generate the final class:"
334                     + " %s",
335                 finalExtensions.stream().map(this::extensionName).collect(joining(", ")));
336         break;
337     }
338     return ImmutableList.copyOf(applicableExtensions);
339   }
340 
methodsConsumedByExtensions( TypeElement type, ImmutableList<AutoValueExtension> applicableExtensions, ExtensionContext context, ImmutableSet<ExecutableElement> abstractMethods, ImmutableMap<String, ExecutableElement> properties)341   private ImmutableSet<ExecutableElement> methodsConsumedByExtensions(
342       TypeElement type,
343       ImmutableList<AutoValueExtension> applicableExtensions,
344       ExtensionContext context,
345       ImmutableSet<ExecutableElement> abstractMethods,
346       ImmutableMap<String, ExecutableElement> properties) {
347     Set<ExecutableElement> consumed = new HashSet<>();
348     for (AutoValueExtension extension : applicableExtensions) {
349       Set<ExecutableElement> consumedHere = new HashSet<>();
350       for (String consumedProperty : extension.consumeProperties(context)) {
351         ExecutableElement propertyMethod = properties.get(consumedProperty);
352         if (propertyMethod == null) {
353           errorReporter()
354               .reportError(
355                   type,
356                   "[AutoValueConsumeNonexist] Extension %s wants to consume a property that does"
357                       + " not exist: %s",
358                   extensionName(extension),
359                   consumedProperty);
360         } else {
361           consumedHere.add(propertyMethod);
362         }
363       }
364       for (ExecutableElement consumedMethod : extension.consumeMethods(context)) {
365         if (!abstractMethods.contains(consumedMethod)) {
366           errorReporter()
367               .reportError(
368                   type,
369                   "[AutoValueConsumeNotAbstract] Extension %s wants to consume a method that is"
370                       + " not one of the abstract methods in this class: %s",
371                   extensionName(extension),
372                   consumedMethod);
373         } else {
374           consumedHere.add(consumedMethod);
375         }
376       }
377       for (ExecutableElement repeat : intersection(consumed, consumedHere)) {
378         errorReporter()
379             .reportError(
380                 repeat,
381                 "[AutoValueMultiConsume] Extension %s wants to consume a method that was already"
382                     + " consumed by another extension",
383                 extensionName(extension));
384       }
385       consumed.addAll(consumedHere);
386     }
387     return ImmutableSet.copyOf(consumed);
388   }
389 
validateMethods( TypeElement type, ImmutableSet<ExecutableElement> abstractMethods, ImmutableSet<ExecutableElement> toBuilderMethods, ImmutableSet<ExecutableElement> propertyMethods, boolean extensionsPresent)390   private void validateMethods(
391       TypeElement type,
392       ImmutableSet<ExecutableElement> abstractMethods,
393       ImmutableSet<ExecutableElement> toBuilderMethods,
394       ImmutableSet<ExecutableElement> propertyMethods,
395       boolean extensionsPresent) {
396     for (ExecutableElement method : abstractMethods) {
397       if (propertyMethods.contains(method)) {
398         checkReturnType(type, method);
399       } else if (!toBuilderMethods.contains(method)
400           && objectMethodToOverride(method) == ObjectMethod.NONE) {
401         // This could reasonably be an error, were it not for an Eclipse bug in
402         // ElementUtils.override that sometimes fails to recognize that one method overrides
403         // another, and therefore leaves us with both an abstract method and the subclass method
404         // that overrides it. This shows up in AutoValueTest.LukesBase for example.
405         String extensionMessage = extensionsPresent ? ", and no extension consumed it" : "";
406         errorReporter()
407             .reportWarning(
408                 method,
409                 "[AutoValueBuilderWhat] Abstract method is neither a property getter nor a Builder"
410                     + " converter%s",
411                 extensionMessage);
412       }
413     }
414     errorReporter().abortIfAnyError();
415   }
416 
extensionName(AutoValueExtension extension)417   private String extensionName(AutoValueExtension extension) {
418     return extension.getClass().getName();
419   }
420 
defineVarsForType( TypeElement type, AutoValueTemplateVars vars, ImmutableSet<ExecutableElement> toBuilderMethods, ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, Optional<BuilderSpec.Builder> maybeBuilder)421   private void defineVarsForType(
422       TypeElement type,
423       AutoValueTemplateVars vars,
424       ImmutableSet<ExecutableElement> toBuilderMethods,
425       ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
426       Optional<BuilderSpec.Builder> maybeBuilder) {
427     ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
428     vars.toBuilderMethods =
429         toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList());
430     vars.toBuilderConstructor = !vars.toBuilderMethods.isEmpty();
431     ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields =
432         propertyFieldAnnotationMap(type, propertyMethods);
433     ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods =
434         propertyMethodAnnotationMap(type, propertyMethods);
435     vars.props =
436         propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods);
437     // Check for @AutoValue.Builder and add appropriate variables if it is present.
438     maybeBuilder.ifPresent(
439         builder -> {
440           ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
441               propertyNameToMethodMap(propertyMethods).inverse();
442           builder.defineVarsForAutoValue(vars, methodToPropertyName);
443           vars.builderName = "Builder";
444           vars.builderAnnotations = copiedClassAnnotations(builder.builderType());
445         });
446   }
447 
448   @Override
nullableAnnotationForMethod(ExecutableElement propertyMethod)449   Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
450     return nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType());
451   }
452 
prefixedGettersIn(Iterable<ExecutableElement> methods)453   static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) {
454     ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder();
455     for (ExecutableElement method : methods) {
456       String name = method.getSimpleName().toString();
457       // TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is.
458       boolean get = name.startsWith("get") && !name.equals("get");
459       boolean is =
460           name.startsWith("is")
461               && !name.equals("is")
462               && method.getReturnType().getKind() == TypeKind.BOOLEAN;
463       if (get || is) {
464         getters.add(method);
465       }
466     }
467     return getters.build();
468   }
469 
ancestorIsAutoValue(TypeElement type)470   private boolean ancestorIsAutoValue(TypeElement type) {
471     while (true) {
472       TypeMirror parentMirror = type.getSuperclass();
473       if (parentMirror.getKind() == TypeKind.NONE) {
474         return false;
475       }
476       TypeElement parentElement = (TypeElement) typeUtils().asElement(parentMirror);
477       if (hasAnnotationMirror(parentElement, AUTO_VALUE_NAME)) {
478         return true;
479       }
480       type = parentElement;
481     }
482   }
483 
implementsAnnotation(TypeElement type)484   private boolean implementsAnnotation(TypeElement type) {
485     return typeUtils().isAssignable(type.asType(), getTypeMirror(Annotation.class));
486   }
487 
getTypeMirror(Class<?> c)488   private TypeMirror getTypeMirror(Class<?> c) {
489     return processingEnv.getElementUtils().getTypeElement(c.getName()).asType();
490   }
491 
immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b)492   private static <E> ImmutableSet<E> immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b) {
493     if (Collections.disjoint(a, b)) {
494       return a;
495     } else {
496       return ImmutableSet.copyOf(difference(a, b));
497     }
498   }
499 }
500