Skip to content

Add support for Views to IsIterableLike #6674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 16, 2018

Conversation

julienrf
Copy link
Contributor

@julienrf julienrf commented May 23, 2018

The conversion from the Repr type now returns an IterableOps[A, CC, C],
whereas it previously had to return an IterableOps[A, Iterable, Repr].

This change makes it possible to create instances of IsIterableLike
for View collections.

Also, the conversion member has been refactored into an apply method.

In addition to IsIterableLike and IsSeqLike, the types IsMapLike
and IsImmutableMapLike have been introduced.

Last, the IsXxxLike types now form a hierarchy: IsImmutableMapLike specializes
IsMapLike, which itself specializes IsIterableLike.

@julienrf julienrf requested review from szeiger and lrytz May 23, 2018 14:00
@scala-jenkins scala-jenkins added this to the 2.13.0-M5 milestone May 23, 2018
}

testIterableOps
testIterable
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the testIterable part which was just duplicating what we had in testIterableOps. And I added more test cases to the testIterableOps (to test with Views and Maps).

@julienrf julienrf force-pushed the extensibility-framework branch from bc6cfc0 to ff003be Compare May 23, 2018 14:06
@julienrf
Copy link
Contributor Author

Note: after we merge this PR we should update that section of the FAQ to say that it’s not anymore needed to use scala-collection-contrib: everything is now provided in the std lib.

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but I'd like @szeiger to review it, too.

}

object IsSeqLike {
import scala.language.higherKinds

implicit val stringRepr: IsSeqLike[String] { type A = Char } =
implicit def seqOpsIsIterableLike[CC0[X] <: SeqOps[X, Iterable, CC0[X]], A0]: IsSeqLike[CC0[A0]] { type A = A0; type C = CC0[A0] } =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be named seqOpsIsSeqLike ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

}

implicit def rangIsSeqLike[C0 <: Range]: IsSeqLike[C0] { type A = Int; type C = immutable.IndexedSeq[Int] } =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed / why isn't seqOpsIsSeqLike enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because seqOpsIsSeqLike provides an instance that matches a type constructor CC0[X] with one type parameter. Range takes no type parameter so it can never be unified with CC0[X].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Don't we have the same issue with BitSets?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, good catch! I’ve updated the PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but then also LongMap? Others?

Copy link
Contributor Author

@julienrf julienrf Jun 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. :-/

But unfortunately a generic definition like the following doesn’t work:

implicit def iterableIsIterableLike[Repr <: Iterable[A0], A0]: IsIterableLike[Repr] { type A = A0; type C = Iterable[A0 } =

Beause scalac always infers Nothing for A0:

type arguments [LongMap[String],Nothing] do not conform to method iterableIsIterableLike's type parameter bounds [Repr <: Iterable[A0],A0]

Maybe there is a trick to get A0 correctly inferred?

Copy link
Contributor

@Jasper-M Jasper-M Aug 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@julienrf I don't know if this is still useful to anyone, but this?

implicit def iterableIsIterableLike[Repr <: Iterable[A0], A0]: IsIterableLike[Repr with Iterable[A0]] { type A = A0; type C = Iterable[A0] } = ???

@julienrf julienrf force-pushed the extensibility-framework branch 2 times, most recently from 45a9556 to 96d0ef9 Compare June 1, 2018 14:32
Copy link
Contributor

@szeiger szeiger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced by these additions. Maybe @milessabin as the original author of IsTraversableLike would like to weigh in on this PR.

}

// 2. Sorted Map collections
implicit def sortedMapIsImmutableMapLike[CC0[X, +Y] <: immutable.Map[X, Y] with immutable.SortedMapOps[X, Y, CC0, CC0[X, Y]], K0, V0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? SortedMap extends Map.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because CC0[X, Y] <: SortedMap[X, Y] gives us CC0[X, Y] <: MapOps[X, Y, Map, CC0[X, Y]], whereas the mapIsImmutableMapLike definition requires CC0[X, Y] <: MapOps[X, Y, CC0, CC0[X, Y]] (note the third type parameter applied to MapOps).

type A
/** A conversion from the representation type `Repr` to `IterableOps[A, Iterable, Repr]`. */
val conversion: Repr => IterableOps[A, Iterable, Repr]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsTraversableLike in 2.12 calls it conversion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can call it back conversion. I think I initially made it a method following up that comment.

*
* @tparam Repr Collection type (e.g. `Map[Int, String]`)
*/
trait IsMapLike[Repr] extends IsIterableLike[Repr] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me what functionality this adds. IsIterableLike and IsSeqLike can abstract over String and Array in addition to collections. There is no such case for maps.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is MapView that doesn’t extend Map.

}

// 2. MapView
implicit def mapViewIsMapLike[CC[X, Y] <: MapView[X, Y], K0, V0]: IsMapLike[CC[K0, V0]] { type K = K0; type V = V0; type C = View[(K, V)] } =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is MapView treated specially? MapView extends MapOps.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same reason as with SortedMap above.

@lrytz lrytz added the WIP label Jun 4, 2018
@lrytz
Copy link
Member

lrytz commented Jun 4, 2018

Marked it as WIP for now; @julienrf can you investigate the options for writing extension methods and bring them in as test cases?

@julienrf julienrf force-pushed the extensibility-framework branch 3 times, most recently from 78c6438 to 1ad3a53 Compare June 7, 2018 14:37
@julienrf
Copy link
Contributor Author

julienrf commented Jun 7, 2018

I’ve come up with a quite complicated but powerful design. This API targets advanced users so maybe the complexity is worth it…

First, let me restate the goal of this work: let users define extension methods on collections in a generic way (so that the operation can be applied to a Seq as well as a View as well as a String, for instance). This is especially useful when working with sequences, because String and Array are not part of the collections hierarchy. This is also useful for immutable Maps because these collections have crazy type signatures that people don’t want to write by hand.

We already have something called IsIterableLike in the standard library but it doesn’t work well with views at the moment (well, only SeqView and MapView, actually). This is because IsIterableLike[Repr] requires a conversion Repr => IterableOps[A, Iterable, Repr], but unfortunately this conversion can not be implemented for SeqView and MapView. Indeed, SeqView[A] can only be converted to IterableOps[A, View, View[A]], but not IterableOps[A, View, SeqView[A]] as it is required by IsIterableLike.

The solution explored in this PR is to generalize IsIterableLike[Repr] so that it now provides an IterableOps[A, Iterable, C], for some types A and C (in particular, C doesn’t have to be equal to Repr):

trait IsIterableLike[Repr] {
  type A
  type C
  def apply(coll: Repr): IterableOps[A, Iterable, C]
}

If you look at the tests you will see that it makes it possible to implement quite complicated scenarios, like the splitWith method (taken from scala/collection-strawman#286), which would have the following type signature if it was defined in Seq:

trait SeqOps[A, CC[_], C] {
  def splitWith(p: A => Boolean): CC[CC[A]]
}

This operation returns an Array[Array[A]] when applied to an Array[A], and an IndexedSeq[String] when applied to a String (and it also works as expected when applied to a List[A] or a View[A]).

@julienrf julienrf force-pushed the extensibility-framework branch from 1ad3a53 to e76239b Compare June 7, 2018 15:00
Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally agree about having this in its complexity. The documentation of IsIterableLike should also mention / link to less generic ways of extending collections (We have / will have that in on the doc site, right?).

It's not clear to me why some parts of the collections hierarchy are mirrored here (immutable.Map) but not others.

For the SortedMap speical case, (why) don't we have the same issue with SortedSet?

There are still various missing instances: LongMap, IntMap, AnyRefMap. Also:

scala> class ExtensionMethods[Repr, I <: collection.generic.IsIterableLike[Repr]](coll: Repr, it: I) { def foo = 1 }
scala> implicit def withExtensions[Repr](coll: Repr)(implicit it: collection.generic.IsIterableLike[Repr]): 

scala> List().foo
res0: Int = 1

scala> Nil.foo
           ^
       error: value foo is not a member of object Nil

@@ -87,55 +85,70 @@ import scala.reflect.ClassTag
* instance of `IsIterableLike` specific to the new type.
*
* Below is an example of an implementation of the `IsIterableLike` trait
* where the `Repr` type is `String`.
* where the `Repr` type is `Range`.
Copy link
Member

@lrytz lrytz Jun 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current form, I think this is not the best example because the actual implicit for Range implements IsSeqLike.

I think it would make sense to first talk about IsSeqLike and IsMapLike here, and say why they exist. Then it's good to give rangeIsSeqLike as an example (and say that this is how it's actually implemented).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping ☝️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal of this documentation is not to explain how things actually work, but how users can implement IsIterableLike instances. So, I think it’s fine to use Range as an example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but then add a comment saying that this is an example, the actual implementation for ranges exists and doesn't need to be written by the user, and it uses IsSeqLike.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* has keys of type `K`, values of type `V` and has a conversion to `MapOps[K, V, _, _]`.
*
* This type enables simple enrichment of `Map`s with extension methods.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add @see [[scala.collection.generic.IsIterableLike]]

// `Range` can not be unified with the `CC0` parameter of the
// `seqOpsIsSeqLike` definition because it does not take a type parameter.
// Hence the need for a separate case:
implicit def rangIsSeqLike[C0 <: Range]: IsSeqLike[C0] { type A = Int; type C = immutable.IndexedSeq[Int] } =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo rang**e**IsSeqLike

import scala.language.higherKinds

/**
* Type class witnessing that a collection type `C`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C -> Repr?


type A = (K, V)

/** A conversion from the type `C` to `MapOps[K, V, _, _]` */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C -> Repr?

def apply(coll: CC0[A0]): SeqOps[A0, View, View[A0]] = coll
}

implicit def stringIsSeqLike: IsSeqLike[String] { type A = Char; type C = String } =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be a val

}

object IsSeqLike {
import scala.language.higherKinds

implicit val stringRepr: IsSeqLike[String] { type A = Char } =
implicit def seqOpsIsSeqLike[CC0[X] <: SeqOps[X, Iterable, CC0[X]], A0]: IsSeqLike[CC0[A0]] { type A = A0; type C = CC0[A0] } =
new IsSeqLike[CC0[A0]] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use a single instance and a cast for these methods?

def apply(c: CC0[K0, V0]): immutable.MapOps[K0, V0, MapCC, C] = c
}

// 2. Sorted Map collections
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment why this is needed

val splitted9 = "foo".splitWith(_.isLower)
val splitted9T: IndexedSeq[String] = splitted9
val splitted10 = "foo".view.splitWith(_.isLower)
val splitted10T: View[View[Char]] = splitted10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for testing type inference

@julienrf
Copy link
Contributor Author

It's not clear to me why some parts of the collections hierarchy are mirrored here (immutable.Map) but not others.

There is no good reason for that, actually.

I implemented these classes when working on extension methods, in the scala-collection-contrib module. It turns out that so far we haven’t had to write extension methods for Set collections, so I didn’t need to create IsSetLike.

I’m not sure we should mirror each level of the hierarchy, though. For sure, IsSeqLike is really useful but maybe IsMapLike and IsImmutableMapLike could stay in scala-collection-contrib.

For the SortedMap speical case, (why) don't we have the same issue with SortedSet?

We don’t need it because both Set and SortedSet match the iterableOpsIsIterableLike definition (they can both be unified to a CC[X] <: IterableOps[X, Iterable, _] type), whereas immutable.SortedMap doesn’t match the mapIsImmutableMapLike definition.

There are still various missing instances: LongMap, IntMap, AnyRefMap

Yes, it’s unfortunate but if we try to create a more general case like the following:

def foo[C <: IterableOps[A, Iterable, C], A]: IsIterableLike[C] =

(which should theoretically work for List[Int] as well as BitSet or even LongMap[String])

It doesn’t work due to type inference issues. The type inferencer is unable to correctly instantiate the type parameter A (it always instantiate it to Nothing). Maybe there is some trick to fix that?

That’s why we have to add special cases for BitSet, IntMap and LongMap.

@julienrf julienrf force-pushed the extensibility-framework branch 4 times, most recently from cac00af to 8b0db52 Compare June 14, 2018 09:12
@julienrf
Copy link
Contributor Author

julienrf commented Jun 14, 2018

Following up our discussion I’ve removed IsImmutableMapLike: if we go to that level of details then we should have an IsXxxLike for every node of the hierarchy…

So, in this PR, we have:

  • IsIterableLike and IsSeqLike have been generalized to work with View and SeqView,
  • IsMapLike is introduced. Wy IsMapLike but not also IsSetLike? Well, IsMapLike can be useful to abstract over both Map and MapView. We don’t have this problem with Set because there is no SetView (yet?), so IsIterableLike is enough if we want to abstract over Set and View.

I think this is good to go so I’m removing the “wip“ label.

@milessabin
Copy link
Contributor

I'd go with IsIterable.

@lrytz
Copy link
Member

lrytz commented Aug 8, 2018

OK, so let's remove IsMapLike, make sure the documentation shows how to write extension methods on maps, and rename to IsIterable also sounds good to me. @julienrf do you have time to do that?

@julienrf
Copy link
Contributor Author

julienrf commented Aug 8, 2018

I can probably do it before the end of next week.

}
b.result()
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve tried to write a decorator that uses MapOps instead of IsMapLike but it doesn’t work:

    implicit class MapDecorator[M <: MapOps[K, V, CC, C], K, V, CC[X, Y] <: IterableOps[_, AnyConstr, _], C](coll: M) {
      def leftOuterJoin[W, That](other: Map[K, W])(implicit bf: BuildFrom[M, (K, (V, Option[W])), That]): That = {
        val b = bf.newBuilder(coll)
        for ((k, v) <- coll) {
          b += k -> (v, other.get(k))
        }
        b.result()
      }
      def rightOuterJoin[W, That](other: Map[K, W])(implicit bf: BuildFrom[M, (K, (Option[V], W)), That]): That = {
        val b = bf.newBuilder(coll)
        for ((k, w) <- other) {
          b += k -> (coll.get(k), w)
        }
        b.result()
      }
    }

The implicit conversion is never triggered. If I try to explicitly call the conversion, I get type errors like so:

[error] inferred type arguments [scala.collection.immutable.Map[Int,String],Nothing,Nothing,Nothing,Nothing] do not conform to method MapDecorator's type parameter bounds [M <: scala.collection.MapOps[K,V,CC,C],K,V,CC[X, Y] <: scala.collection.IterableOps[_, collection.AnyConstr, _],C]
[error]     val mapLJoin = MapDecorator(map).leftOuterJoin(Map(1 -> "bar"))
[error]                    ^

I think users will need to define an intermediate type for the type unification to work. This is exactly what IsMapLike does, so why not just keep it instead of telling users to copy it everywhere?

@julienrf julienrf force-pushed the extensibility-framework branch from fbe9065 to 3403e0c Compare August 14, 2018 12:36
@szeiger
Copy link
Contributor

szeiger commented Aug 14, 2018

You don't need M (which can't be properly inferred by the compiler). This works (replace M by C in the rest of the snippet):

    implicit class MapDecorator[K, V, CC[_, _] <: IterableOps[_, Any, _], C](coll: MapOps[K, V, CC, C] with C) {

@SethTisue
Copy link
Member

@szeiger is your objection a blocker for M5, or can we merge this for M5 and then fix it for RC1?

@lrytz lrytz force-pushed the extensibility-framework branch from 3403e0c to b68645c Compare August 16, 2018 11:23
@lrytz
Copy link
Member

lrytz commented Aug 16, 2018

I rebased this on current 2.13.x. Note that the DecoratorsTest now starts failing to compile due to an implicit ambiguity introduced by #7006 (cc @joshlemer).

@joshlemer
Copy link
Contributor

@lrytz will look into it, thanks

@lrytz
Copy link
Member

lrytz commented Aug 16, 2018

I pushed a commit that reverts 6644d68, but just for testing. And I pushed a commit that removes IsMap and changes the map decorators test as Stefan shows.

For reference, the reason why IsMap is not necessary is that IsMap has only instances for MapOps subtypes. IsSeq (and therefore IsIterable, IsIterableOnce) on the other hand has instances for String and Array. So using IsSeq instead of SeqOps allows extending Array and String at the same time.

@joshlemer
Copy link
Contributor

I think maybe we should revert 6644d68 for now, since that problem is quite a bit less important than this pull request, and it seems like a lot of tests introduced in this ticket break buildFromIterableOnce so maybe a different approach is required.

@lrytz lrytz force-pushed the extensibility-framework branch from 016479d to 3a35468 Compare August 16, 2018 12:47
type C = View[(K0, V0)]
def apply(coll: CC0[K0, V0]): IterableOps[A, Iterable, View[(K0, V0)]] = coll
}

Copy link
Member

@lrytz lrytz Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need explicit IsIterable instances for maps, I assume due to MapOps having a different number of type parameters. I added the two instances here that should make existing tests pass. This is however incomplete, we also need instances for AnyRefMap, IntMap, LongMap.

So in the end we don't gain much by removing IsMap in terms of lines of code. In this case, I have a preference to keep it.

@julienrf @SethTisue @szeiger what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it’s simpler to keep IsMap. Otherwise, we can check carefully that all the previous IsMap instances now have a corresponding IsIterable instance.

@julienrf
Copy link
Contributor Author

julienrf commented Aug 16, 2018

@szeiger

You don't need M (which can't be properly inferred by the compiler). This works (replace M by C in the rest of the snippet):

    implicit class MapDecorator[K, V, CC[_, _] <: IterableOps[_, Any, _], C](coll: MapOps[K, V, CC, C] with C) {

I think this solution works by chance. For instance, for MapView[K, V] the type parameters would be unified as follows: CC = MapView and C = View[(K, V)]. However, if we base our BuildFrom implicit parameter on the C type parameter, this means that we lose the information that the receiver of the extension method is a MapView (and not just a View). In our case that changes nothing, but we could easily imagine a situation where one wants to build another MapView from a MapView, and not a View.

@lrytz
Copy link
Member

lrytz commented Aug 16, 2018

OK, I think we should merge #7088, then rebase this PR and get it in with your two commits (include IsMap).

@SethTisue
Copy link
Member

#7088 is merged

The conversion from the `Repr` type now returns an `IterableOps[A, CC, C]`,
whereas it previously had to return an `IterableOps[A, Iterable, Repr]`.

This change makes it possible to create instances of `IsIterableLike`
for View collections.

Also, the `conversion` member has been refactored into an `apply` method.

In addition to `IsIterableLike` and `IsSeqLike`, the type `IsMapLike`
has been introduced.

Last, the `IsXxxLike` types now form a hierarchy: `IsMapLike` specializes
`IsSeqLike`, which itself specializes `IsIterableLike`.

Fixes scala/collection-strawman#535
@lrytz lrytz force-pushed the extensibility-framework branch from 3a35468 to 57a8ba4 Compare August 16, 2018 18:34
@lrytz lrytz merged commit 7b5fc10 into scala:2.13.x Aug 16, 2018
@julienrf julienrf deleted the extensibility-framework branch August 16, 2018 19:31
// Makes `IsMap` instances visible in `IsIterable` companion
implicit def isMapLikeIsIterable[Repr](implicit
isMapLike: IsMap[Repr]
): IsIterable[Repr] { type A = isMapLike.A; type C = isMapLike.C } = isMapLike
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to isMapIsIterable ? 🤔

  implicit def isMapIsIterable[Repr](implicit
    isMap: IsMap[Repr]
  ): IsIterable[Repr] { type A = isMap.A; type C = isMap.C } = isMap

// Makes `IsSeq` instances visible in `IsIterable` companion
implicit def isSeqLikeIsIterable[Repr](implicit
isSeqLike: IsSeq[Repr]
): IsIterable[Repr] { type A = isSeqLike.A; type C = isSeqLike.C } = isSeqLike
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as isMapIsIterable

SethTisue added a commit to scalacommunitybuild/shapeless that referenced this pull request Aug 18, 2018
@SethTisue SethTisue added release-notes worth highlighting in next release notes and removed release-notes worth highlighting in next release notes labels Aug 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
prio:blocker release blocker (used only by core team, only near release time) release-notes worth highlighting in next release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants