bug in DiffRenderContext
Zhen-hao opened this issue · 7 comments
It is triggered by user code, but I think the bug in how diff is rendered.
If I use access.transition
the failure is silent. access.syncTransition
makes the issue clear.
[info] scala.MatchError: 3 (of class java.lang.Byte)
[info] at levsha.impl.DiffRenderContext.deleteLoop(DiffRenderContext.scala:408)
[info] at levsha.impl.DiffRenderContext.diff(DiffRenderContext.scala:242)
[info] at korolev.internal.ApplicationInstance.$anonfun$onState$8(ApplicationInstance.scala:119)
[info] at korolev.internal.ApplicationInstance.$anonfun$onState$8$adapted(ApplicationInstance.scala:119)
[info] at korolev.internal.Frontend.$anonfun$performDomChanges$1(Frontend.scala:189)
[info] at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
[info] at korolev.effect.Effect$FutureEffect.delay(Effect.scala:78)
[info] at korolev.effect.Effect$FutureEffect.delay(Effect.scala:66)
[info] at korolev.internal.Frontend.performDomChanges(Frontend.scala:187)
[info] at korolev.internal.ApplicationInstance.$anonfun$onState$7(ApplicationInstance.scala:119)
[info] at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:434)
[info] at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:69)
[info] at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:393)
[info] at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info] at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:276)
[info] at scala.concurrent.impl.Promise$DefaultPromise.flatMap(Promise.scala:140)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:86)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:66)
[info] at korolev.effect.syntax$EffectOps.flatMap(syntax.scala:36)
[info] at korolev.internal.ApplicationInstance.$anonfun$onState$5(ApplicationInstance.scala:108)
[info] at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:434)
[info] at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:69)
[info] at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:393)
[info] at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info] at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:276)
[info] at scala.concurrent.impl.Promise$DefaultPromise.flatMap(Promise.scala:140)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:86)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:66)
[info] at korolev.effect.syntax$EffectOps.flatMap(syntax.scala:36)
[info] at korolev.internal.ApplicationInstance.$anonfun$onState$1(ApplicationInstance.scala:105)
[info] at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:434)
[info] at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:69)
[info] at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:393)
[info] at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info] at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:276)
[info] at scala.concurrent.impl.Promise$DefaultPromise.flatMap(Promise.scala:140)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:86)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:66)
[info] at korolev.effect.syntax$EffectOps.flatMap(syntax.scala:36)
[info] at korolev.internal.ApplicationInstance.onState(ApplicationInstance.scala:103)
[info] at korolev.internal.ApplicationInstance.$anonfun$topLevelComponentInstance$2(ApplicationInstance.scala:84)
[info] at korolev.internal.ComponentInstance.$anonfun$applyTransition$4(ComponentInstance.scala:280)
[info] at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:434)
[info] at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:69)
[info] at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:393)
[info] at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info] at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:276)
[info] at scala.concurrent.impl.Promise$DefaultPromise.flatMap(Promise.scala:140)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:86)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:66)
[info] at korolev.effect.syntax$EffectOps.flatMap(syntax.scala:36)
[info] at korolev.internal.ComponentInstance.$anonfun$applyTransition$2(ComponentInstance.scala:279)
[info] at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:434)
[info] at korolev.effect.Effect$FutureEffect$$anon$1.execute(Effect.scala:69)
[info] at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:393)
[info] at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:302)
[info] at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:276)
[info] at scala.concurrent.impl.Promise$DefaultPromise.flatMap(Promise.scala:140)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:86)
[info] at korolev.effect.Effect$FutureEffect.flatMap(Effect.scala:66)
[info] at korolev.effect.syntax$EffectOps.flatMap(syntax.scala:36)
[info] at korolev.internal.ComponentInstance.$anonfun$applyTransition$1(ComponentInstance.scala:277)
[info] at korolev.internal.ComponentInstance.applyTransition(ComponentInstance.scala:282)
[info] at korolev.internal.ComponentInstance$browserAccess$.syncTransition(ComponentInstance.scala:140)
[info] at com.nt.front.UiWebService.$anonfun$config$433(UiWebService.scala:2793)
I found the cause from user code:
select(id := "dropdown-list",
option(value := "choice1", "Cat", if (true) selected else void),
option(value := "choice2", "Dog", if (false) selected else void),
selectorId)
though this type checks, but it breaks rendering.
The following works fine.
select(id := "dropdown-list",
if (true) option(value := "choice1", "Cat", selected) else option(value := "choice1", "Cat"),
if (false) option(value := "choice2", "Dog", selected) else option(value := "choice2", "Dog"),
selectorId)
maybe my understanding of void
is wrong.
but it would be great to catch this at compile time.
It's known problem. If levsha.macros.unableToSortTagWarnings
sys property is true, you will get warning about this. Unfortunately I never added it to documentation. Try:
select(id := "dropdown-list",
option(value := "choice1", if (true) selected else void, "Cat"),
option(value := "choice2", if (false) selected else void, "Dog"),
selectorId
)
Also you can use when
as shortcut for if (cond) node else void
.
The problem explanation: DiffRenderContext requires strict order of Document kinds. First goes styles, then attributes, then nodes. Content of tag can be sorted in runtime, but optimeze
macro should do it in compile time. Unfortunately, sometimes it's not possible to infer kind of a Document in compile time, so it leads to the problem.
It your case:
select(
id := "dropdown-list",
option(
value := "choice1", // Attr
"Cat", // TextNode
if (true) selected else void // Attr
),
selectorId
)
Thanks for the explanation!
It would be very helpful to add a comment about the importance of ordering in the documentation.
The problem was partially fixed in Levsha 1.0.0. Now if tag content couldn't be sorted during optimization it will be leaved unoptimized. However you can force optimization for these cases if you want. See optimizer options.
BTW, when I try to use the when
constructor, I got errors like this
inferred type arguments [levsha.Document.Node[com.nt.front.UiState.globalContext.Event]] do not conform to method when's type parameter bounds [D <: levsha.Document[Nothing]]
Error occurred in an application involving default arguments.
I will try it later with version 1.0