diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt index df043060b..e5e37e85a 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt @@ -2,6 +2,7 @@ package com.yandex.div.core.expression import com.yandex.div.core.Disposable import com.yandex.div.core.ObserverList +import com.yandex.div.core.expression.local.RuntimeStore import com.yandex.div.core.expression.variables.ConstantsProvider import com.yandex.div.core.expression.variables.VariableAndConstantController import com.yandex.div.core.expression.variables.VariableController @@ -26,10 +27,15 @@ import com.yandex.div.json.typeMismatch import org.json.JSONObject internal class ExpressionResolverImpl( + /** + * Unique path by which resolver could be found at [RuntimeStore]. + */ + internal val path: String, + internal val runtimeStore: RuntimeStore, private val variableController: VariableController, private val evaluator: Evaluator, private val errorCollector: ErrorCollector, - private val onCreateCallback: OnCreateCallback + private val onCreateCallback: OnCreateCallback, ) : ExpressionResolver { private val evaluationsCache = mutableMapOf() @@ -230,9 +236,12 @@ internal class ExpressionResolverImpl( } } - operator fun plus(constants: ConstantsProvider): ExpressionResolverImpl { + internal fun withConstants(pathSegment: String, + constants: ConstantsProvider): ExpressionResolverImpl { val variableAndConstantController = VariableAndConstantController(variableController, constants) return ExpressionResolverImpl( + path = this.path + "/" + pathSegment, + runtimeStore = this.runtimeStore, variableController = variableAndConstantController, evaluator = Evaluator( evaluationContext = EvaluationContext( @@ -254,6 +263,26 @@ internal class ExpressionResolverImpl( } } + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + if (other !is ExpressionResolverImpl) { + return false + } + + if (this.runtimeStore !== other.runtimeStore) { + return false + } + + return path == other.path + } + + override fun hashCode(): Int { + return 31 * path.hashCode() + runtimeStore.hashCode() + } + /** * ExpressionResolverImpl may create new instance in 'plus' operator. * To be able to registrate all created resolvers and their variables controllers diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt index 1c83bc22c..bae477551 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt @@ -4,17 +4,14 @@ import com.yandex.div.core.DivViewFacade import com.yandex.div.core.expression.local.RuntimeStore import com.yandex.div.core.expression.triggers.TriggersController import com.yandex.div.core.expression.variables.VariableController -import com.yandex.div.internal.Assert -import com.yandex.div.json.expressions.ExpressionResolver internal class ExpressionsRuntime( - val expressionResolver: ExpressionResolver, + val expressionResolver: ExpressionResolverImpl, val variableController: VariableController, val triggersController: TriggersController? = null, val functionProvider: FunctionProviderDecorator, val runtimeStore: RuntimeStore, ) { - private val expressionResolverImpl get() = expressionResolver as? ExpressionResolverImpl private var unsubscribed = true fun clearBinding() { @@ -28,9 +25,7 @@ internal class ExpressionsRuntime( fun updateSubscriptions() { if (unsubscribed) { unsubscribed = false - expressionResolverImpl?.subscribeOnVariables() ?: run { - Assert.fail("ExpressionRuntime must have ExpressionResolverImpl as expressionResolver.") - } + expressionResolver.subscribeOnVariables() variableController.restoreSubscriptions() } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt index d1d2b8122..df410b5f2 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt @@ -147,10 +147,12 @@ internal class ExpressionsRuntimeProvider @Inject constructor( } val expressionResolver = ExpressionResolverImpl( - variableController, - evaluator, - errorCollector, - callback, + path = "dataTag: '${tag.id}'", + runtimeStore = runtimeStore, + variableController = variableController, + evaluator = evaluator, + errorCollector = errorCollector, + onCreateCallback = callback, ) val triggersController = TriggersController( diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt index 88e5e1ac3..4426ff21c 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt @@ -98,7 +98,7 @@ internal class RuntimeStore( internal fun resolveRuntimeWith( path: String, - div: Div, + div: Div?, resolver: ExpressionResolver, parentResolver: ExpressionResolver?, ): ExpressionsRuntime? { @@ -157,6 +157,8 @@ internal class RuntimeStore( val evaluator = Evaluator(evaluationContext) val resolver = ExpressionResolverImpl( + path = path + "/" + (baseRuntime.expressionResolver as? ExpressionResolverImpl)?.path, + runtimeStore = this, variableController = localVariableController, evaluator = evaluator, errorCollector = errorCollector, @@ -185,7 +187,7 @@ internal class RuntimeStore( private fun getRuntimeOrCreateChild( path: String, - div: Div, + div: Div?, existingRuntime: ExpressionsRuntime? = null, parentResolver: ExpressionResolver? = null, parentRuntime: ExpressionsRuntime? = null, @@ -201,9 +203,9 @@ internal class RuntimeStore( val parentRuntime = parentRuntime ?: parentResolver?.let { getRuntimeWithOrNull(it) } - val variables = div.value().variables?.toVariables() - val variableTriggers = div.value().variableTriggers - val functions = div.value().functions + val variables = div?.value()?.variables?.toVariables() + val variableTriggers = div?.value()?.variableTriggers + val functions = div?.value()?.functions if (needLocalRuntime(variables, variableTriggers, functions)) { return createChildRuntime(runtime, parentRuntime, path, variables, variableTriggers, functions) diff --git a/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt b/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt index 98380e0b4..004333cfb 100644 --- a/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt +++ b/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt @@ -3,6 +3,7 @@ package com.yandex.div.internal.core import com.yandex.div.core.annotations.InternalApi import com.yandex.div.core.expression.ExpressionResolverImpl import com.yandex.div.core.expression.variables.ConstantsProvider +import com.yandex.div.internal.Assert import com.yandex.div.internal.util.forEach import com.yandex.div.internal.util.mapIndexedNotNull import com.yandex.div.json.expressions.ExpressionResolver @@ -69,7 +70,20 @@ private fun DivCollectionItemBuilder.getItemResolver( dataElementName to validElement, INDEX_VARIABLE_NAME to index.toLong() )) - return resolverImpl + localDataProvider + + val itemResolver = resolverImpl.withConstants( + pathSegment = "${dataElement}:$index", localDataProvider) + val runtime = resolverImpl.runtimeStore.resolveRuntimeWith( + itemResolver.path, + div = null, + resolver = itemResolver, + parentResolver = resolverImpl, + ) + return runtime?.expressionResolver ?: run { + Assert.fail( + "Failed to acquire ExpressionResolver from store! This may lead to leaks and errors!") + itemResolver + } } private fun Div.copy(id: String? = value().id): Div { diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt index 276bf8ad3..f84a037a2 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt @@ -81,6 +81,8 @@ class ExpressionResolverImplTest { ) private val underTest = ExpressionResolverImpl( + "", + mock(), externalVariables, Evaluator(evaluationContext), mock() @@ -88,6 +90,8 @@ class ExpressionResolverImplTest { private val withFuncGetCallback = { callback: () -> Unit -> ExpressionResolverImpl( + "", + mock(), VariableControllerImpl(), Evaluator( EvaluationContext( diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt index 480b7af2a..fc1a5a6cd 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt @@ -51,14 +51,14 @@ class RuntimeStoreTest { underTest.putRuntime(runtimeFromCallback!!) } - private val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback) + private val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) private val rootVariableController: VariableController = mock() private val functionProvider = mock() private val divBase = mock() private val div = mock
{ on { value() } doReturn divBase } - private val rootResolver = ExpressionResolverImpl(rootVariableController, evaluator, errorCollector, callback) + private val rootResolver = ExpressionResolverImpl("", mock(), rootVariableController, evaluator, errorCollector, callback) private val rootRuntime: ExpressionsRuntime = ExpressionsRuntime(rootResolver, rootVariableController, null, functionProvider, underTest) @@ -74,7 +74,7 @@ class RuntimeStoreTest { @Test fun `setPathToRuntimeWith links path to created runtime`() { - val newResolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback) + val newResolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) underTest.resolveRuntimeWith(path.fullPath, div, newResolver, newResolver) Assert.assertNotNull(underTest.getRuntimeWithOrNull(newResolver)) @@ -99,7 +99,7 @@ class RuntimeStoreTest { @Test fun `getOrCreateRuntime returns runtime for path if exist`() { - val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback) + val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) val runtime = ExpressionsRuntime(resolver, mock(), null, functionProvider, underTest) underTest.putRuntime(runtime, PATH, rootRuntime) @@ -110,7 +110,7 @@ class RuntimeStoreTest { @Test fun `getOrCreateRuntime returns parent runtime for path if no variables provided`() { - val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback) + val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) val parentVariableController = mock() val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest) underTest.putRuntime(runtime, PARENT_PATH, rootRuntime) @@ -121,7 +121,7 @@ class RuntimeStoreTest { @Test fun `getOrCreateRuntime returns wrapped parent runtime with new variables if new variables provided`() { - val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback) + val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) val parentVariableController = mock { on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123") } @@ -153,7 +153,7 @@ class RuntimeStoreTest { @Test fun `setPathToRuntimeWith links path to runtime`() { - val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback) + val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) underTest.resolveRuntimeWith(path.fullPath, div, resolver, resolver) Assert.assertNotNull(runtimeFromCallback) @@ -174,7 +174,7 @@ class RuntimeStoreTest { on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123") } val resolver = ExpressionResolverImpl( - parentVariableController, evaluator, errorCollector, callback + "", mock(), parentVariableController, evaluator, errorCollector, callback ) underTest.resolveRuntimeWith(path.fullPath, div, resolver, resolver) diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt index ea6b337ac..b72c24f32 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt @@ -48,7 +48,7 @@ class TwoWayVariableBinderTest { private val evaluator = mock { on { evaluationContext } doReturn evaluationContext } - private val expressionResolver = ExpressionResolverImpl(mock(), evaluator, mock(), mock()) + private val expressionResolver = ExpressionResolverImpl("", mock(), mock(), evaluator, mock(), mock()) private val expressionsRuntime = ExpressionsRuntime(expressionResolver, variableController, mock(), mock(), store) private val expressionsRuntimeProvider = mock { on { getOrCreate(any(), any(), any()) } doReturn expressionsRuntime