summaryrefslogtreecommitdiff
path: root/main/test/src/mill/define/ApplicativeTests.scala
blob: 9dd2132ff06958eafcfbadd28923e9fd49e5b41f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package mill.define

import mill.api.Ctx.ImplicitStub
import utest._

import scala.annotation.compileTimeOnly
import scala.language.experimental.macros


object ApplicativeTests extends TestSuite {
  implicit def optionToOpt[T](o: Option[T]): Opt[T] = new Opt(o)
  class Opt[T](val self: Option[T]) extends Applicative.Applyable[Option, T]
  object Opt extends OptGenerated with Applicative.Applyer[Opt, Option, Applicative.Id, String]{

    val injectedCtx = "helloooo"
    def underlying[A](v: Opt[A]) = v.self
    def apply[T](t: T): Option[T] = macro Applicative.impl[Option, T, String]

    def mapCtx[A, B](a: Option[A])(f: (A, String) => B): Option[B] = a.map(f(_, injectedCtx))
    def zip() = Some(())
    def zip[A](a: Option[A]) = a.map(Tuple1(_))
  }
  class Counter{
    var value = 0
    def apply() = {
      value += 1
      value
    }
  }
  @compileTimeOnly("Target.ctx() can only be used with a T{...} block")
  @ImplicitStub
  implicit def taskCtx: String = ???

  val tests = Tests{

    'selfContained - {

      'simple - assert(Opt("lol " + 1) == Some("lol 1"))
      'singleSome - assert(Opt("lol " + Some("hello")()) == Some("lol hello"))
      'twoSomes - assert(Opt(Some("lol ")() + Some("hello")()) == Some("lol hello"))
      'singleNone - assert(Opt("lol " + None()) == None)
      'twoNones - assert(Opt("lol " + None() + None()) == None)
    }
    'context - {
      assert(Opt(Opt.ctx() + Some("World")()) == Some("hellooooWorld"))
    }
    'capturing - {
      val lol = "lol "
      def hell(o: String) = "hell" + o
      'simple - assert(Opt(lol + 1) == Some("lol 1"))
      'singleSome - assert(Opt(lol + Some(hell("o"))()) == Some("lol hello"))
      'twoSomes - assert(Opt(Some(lol)() + Some(hell("o"))()) == Some("lol hello"))
      'singleNone - assert(Opt(lol + None()) == None)
      'twoNones - assert(Opt(lol + None() + None()) == None)
    }
    'allowedLocalDef - {
      // Although x is defined inside the Opt{...} block, it is also defined
      // within the LHS of the Applyable#apply call, so it is safe to life it
      // out into the `zipMap` arguments list.
      val res = Opt{ "lol " + Some("hello").flatMap(x => Some(x)).apply() }
      assert(res == Some("lol hello"))
    }
    'upstreamAlwaysEvaluated - {
      // Whether or not control-flow reaches the Applyable#apply call inside an
      // Opt{...} block, we always evaluate the LHS of the Applyable#apply
      // because it gets lifted out of any control flow statements
      val counter = new Counter()
      def up = Opt{ "lol " + counter() }
      val down = Opt{ if ("lol".length > 10) up() else "fail" }
      assert(
        down == Some("fail"),
        counter.value == 1
      )
    }
    'upstreamEvaluatedOnlyOnce - {
      // Even if control-flow reaches the Applyable#apply call more than once,
      // it only gets evaluated once due to its lifting out of the Opt{...} block
      val counter = new Counter()
      def up = Opt{ "lol " + counter() }
      def runTwice[T](t: => T) = (t, t)
      val down = Opt{ runTwice(up()) }
      assert(
        down == Some(("lol 1", "lol 1")),
        counter.value == 1
      )
    }
    'evaluationsInsideLambdasWork - {
      // This required some fiddling with owner chains inside the macro to get
      // working, so ensure it doesn't regress
      val counter = new Counter()
      def up = Opt{ "hello" + counter() }
      val down1 = Opt{ (() => up())() }
      val down2 = Opt{ Seq(1, 2, 3).map(n => up() * n) }
      assert(
        down1 == Some("hello1"),
        down2 == Some(Seq("hello2", "hello2hello2", "hello2hello2hello2"))
      )
    }
    'appliesEvaluatedOncePerLexicalCallsite - {
      // If you have multiple Applyable#apply() lexically in the source code of
      // your Opt{...} call, each one gets evaluated once, even if the LHS of each
      // apply() call is identical. It's up to the downstream zipMap()
      // implementation to decide if it wants to dedup them or do other things.
      val counter = new Counter()
      def up = Opt{ "hello" + counter() }
      val down = Opt{ Seq(1, 2, 3).map(n => n + up() + up()) }
      assert(down == Some(Seq("1hello1hello2", "2hello1hello2", "3hello1hello2")))
    }
    'appliesEvaluateBeforehand - {
      // Every Applyable#apply() within a Opt{...} block evaluates before any
      // other logic within that block, even if they would happen first in the
      // normal Scala evaluation order
      val counter = new Counter()
      def up = Opt{ counter() }
      val down = Opt{
        val res = counter()
        val one = up()
        val two = up()
        val three = up()
        (res, one, two, three)
      }
      assert(down == Some((4, 1, 2, 3)))
    }
  }
}