@@ -308,6 +308,160 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
308308 case Block (_, expr) => forallResults(expr, p)
309309 case _ => p(tree)
310310 }
311+
312+ /** Applications in Scala can have one of the following shapes:
313+ *
314+ * 1) naked core: Ident(_) or Select(_, _) or basically anything else
315+ * 2) naked core with targs: TypeApply(core, targs) or AppliedTypeTree(core, targs)
316+ * 3) apply or several applies wrapping a core: Apply(core, _), or Apply(Apply(core, _), _), etc
317+ *
318+ * This class provides different ways to decompose applications and simplifies their analysis.
319+ *
320+ * ***Examples***
321+ * (TypeApply in the examples can be replaced with AppliedTypeTree)
322+ *
323+ * Ident(foo):
324+ * * callee = Ident(foo)
325+ * * core = Ident(foo)
326+ * * targs = Nil
327+ * * argss = Nil
328+ *
329+ * TypeApply(foo, List(targ1, targ2...))
330+ * * callee = TypeApply(foo, List(targ1, targ2...))
331+ * * core = foo
332+ * * targs = List(targ1, targ2...)
333+ * * argss = Nil
334+ *
335+ * Apply(foo, List(arg1, arg2...))
336+ * * callee = foo
337+ * * core = foo
338+ * * targs = Nil
339+ * * argss = List(List(arg1, arg2...))
340+ *
341+ * Apply(Apply(foo, List(arg21, arg22, ...)), List(arg11, arg12...))
342+ * * callee = foo
343+ * * core = foo
344+ * * targs = Nil
345+ * * argss = List(List(arg11, arg12...), List(arg21, arg22, ...))
346+ *
347+ * Apply(Apply(TypeApply(foo, List(targs1, targs2, ...)), List(arg21, arg22, ...)), List(arg11, arg12...))
348+ * * callee = TypeApply(foo, List(targs1, targs2, ...))
349+ * * core = foo
350+ * * targs = Nil
351+ * * argss = List(List(arg11, arg12...), List(arg21, arg22, ...))
352+ */
353+ final class Applied (val tree : Tree ) {
354+ /** The tree stripped of the possibly nested applications.
355+ * The original tree if it's not an application.
356+ */
357+ def callee : Tree = {
358+ @ tailrec
359+ def loop (tree : Tree ): Tree = tree match {
360+ case Apply (fn, _) => loop(fn)
361+ case tree => tree
362+ }
363+ loop(tree)
364+ }
365+
366+ /** The `callee` unwrapped from type applications.
367+ * The original `callee` if it's not a type application.
368+ */
369+ def core : Tree = callee match {
370+ case TypeApply (fn, _) => fn
371+ case AppliedTypeTree (fn, _) => fn
372+ case tree => tree
373+ }
374+
375+ /** The type arguments of the `callee`.
376+ * `Nil` if the `callee` is not a type application.
377+ */
378+ def targs : List [Tree ] = callee match {
379+ case TypeApply (_, args) => args
380+ case AppliedTypeTree (_, args) => args
381+ case _ => Nil
382+ }
383+
384+ /** (Possibly multiple lists of) value arguments of an application.
385+ * `Nil` if the `callee` is not an application.
386+ */
387+ def argss : List [List [Tree ]] = {
388+ def loop (tree : Tree ): List [List [Tree ]] = tree match {
389+ case Apply (fn, args) => loop(fn) :+ args
390+ case _ => Nil
391+ }
392+ loop(tree)
393+ }
394+ }
395+
396+ /** Returns a wrapper that knows how to destructure and analyze applications.
397+ */
398+ final def dissectApplied (tree : Tree ) = new Applied (tree)
399+
400+ /** Equivalent ot disectApplied(tree).core, but more efficient */
401+ @ scala.annotation.tailrec
402+ final def dissectCore (tree : Tree ): Tree = tree match {
403+ case TypeApply (fun, _) =>
404+ dissectCore(fun)
405+ case Apply (fun, _) =>
406+ dissectCore(fun)
407+ case t =>
408+ t
409+ }
410+
411+ /** Destructures applications into important subparts described in `Applied` class,
412+ * namely into: core, targs and argss (in the specified order).
413+ *
414+ * Trees which are not applications are also accepted. Their callee and core will
415+ * be equal to the input, while targs and argss will be Nil.
416+ *
417+ * The provided extractors don't expose all the API of the `Applied` class.
418+ * For advanced use, call `dissectApplied` explicitly and use its methods instead of pattern matching.
419+ */
420+ object Applied {
421+ def apply (tree : Tree ): Applied = new Applied (tree)
422+
423+ def unapply (applied : Applied ): Some [(Tree , List [Tree ], List [List [Tree ]])] =
424+ Some ((applied.core, applied.targs, applied.argss))
425+
426+ def unapply (tree : Tree ): Some [(Tree , List [Tree ], List [List [Tree ]])] =
427+ unapply(dissectApplied(tree))
428+ }
429+
430+ /** Is tree an application with result `this.type`?
431+ * Accept `b.addOne(x)` and also `xs(i) += x`
432+ * where the op is an assignment operator.
433+ */
434+ def isThisTypeResult (tree : Tree )(using Context ): Boolean = tree match {
435+ case Applied (fun @ Select (receiver, op), _, argss) =>
436+ tree.tpe match {
437+ case ThisType (tref) =>
438+ tref.symbol == receiver.symbol
439+ case tref : TermRef =>
440+ tref.symbol == receiver.symbol || argss.exists(_.exists(tref.symbol == _.symbol))
441+ case _ =>
442+ def checkSingle (sym : Symbol ): Boolean =
443+ (sym == receiver.symbol) || {
444+ receiver match {
445+ case Apply (_, _) => op.isOpAssignmentName // xs(i) += x
446+ case _ => receiver.symbol != null &&
447+ (receiver.symbol.isGetter || receiver.symbol.isField) // xs.addOne(x) for var xs
448+ }
449+ }
450+ @ tailrec def loop (mt : Type ): Boolean = mt match {
451+ case m : MethodType =>
452+ m.resType match {
453+ case ThisType (tref) => checkSingle(tref.symbol)
454+ case tref : TermRef => checkSingle(tref.symbol)
455+ case restpe => loop(restpe)
456+ }
457+ case PolyType (_, restpe) => loop(restpe)
458+ case _ => false
459+ }
460+ fun.symbol != null && loop(fun.symbol.info)
461+ }
462+ case _ =>
463+ tree.tpe.isInstanceOf [ThisType ]
464+ }
311465}
312466
313467trait UntypedTreeInfo extends TreeInfo [Untyped ] { self : Trees .Instance [Untyped ] =>
0 commit comments