Skip to content

Commit 02e7b07

Browse files
committed
Add Contracts for DSL methods
1 parent aed113e commit 02e7b07

13 files changed

Lines changed: 347 additions & 138 deletions

File tree

kstatemachine-coroutines/src/commonMain/kotlin/ru/nsk/kstatemachine/statemachine/CoroutinesStateMachine.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import ru.nsk.kstatemachine.coroutines.CoroutinesLibCoroutineAbstraction
1212
import ru.nsk.kstatemachine.coroutines.createStateMachine
1313
import ru.nsk.kstatemachine.event.Event
1414
import ru.nsk.kstatemachine.state.ChildMode
15+
import kotlin.contracts.ExperimentalContracts
16+
import kotlin.contracts.InvocationKind
17+
import kotlin.contracts.contract
1518
import kotlin.coroutines.CoroutineContext
1619
import kotlin.coroutines.EmptyCoroutineContext
1720

@@ -27,15 +30,21 @@ import kotlin.coroutines.EmptyCoroutineContext
2730
*
2831
* Note that all calls to created machine instance should be done only from that thread.
2932
*/
33+
@OptIn(ExperimentalContracts::class)
3034
suspend fun createStateMachine(
3135
scope: CoroutineScope,
3236
name: String? = null,
3337
childMode: ChildMode = ChildMode.EXCLUSIVE,
3438
start: Boolean = true,
3539
creationArguments: CreationArguments = buildCreationArguments {},
3640
init: suspend BuildingStateMachine.() -> Unit
37-
) = CoroutinesLibCoroutineAbstraction(scope)
38-
.createStateMachine(name, childMode, start, creationArguments, init)
41+
): StateMachine {
42+
contract {
43+
callsInPlace(init, InvocationKind.EXACTLY_ONCE)
44+
}
45+
return CoroutinesLibCoroutineAbstraction(scope)
46+
.createStateMachine(name, childMode, start, creationArguments, init)
47+
}
3948

4049
/**
4150
* Processes event in async fashion (using launch() to start new coroutine).

kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/coroutines/CoroutineAbstraction.kt

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ package ru.nsk.kstatemachine.coroutines
1010
import ru.nsk.kstatemachine.state.ChildMode
1111
import ru.nsk.kstatemachine.statemachine.*
1212
import ru.nsk.kstatemachine.statemachine.StateMachineImpl
13+
import kotlin.contracts.ExperimentalContracts
14+
import kotlin.contracts.InvocationKind
15+
import kotlin.contracts.contract
1316
import kotlin.coroutines.Continuation
1417
import kotlin.coroutines.EmptyCoroutineContext
1518
import kotlin.coroutines.startCoroutine
@@ -52,18 +55,24 @@ internal class StdLibCoroutineAbstraction : CoroutineAbstraction {
5255
override suspend fun <R : Any> withContext(block: suspend () -> R): R = block()
5356
}
5457

58+
@OptIn(ExperimentalContracts::class)
5559
suspend fun CoroutineAbstraction.createStateMachine(
5660
name: String?,
5761
childMode: ChildMode,
5862
start: Boolean,
5963
creationArguments: CreationArguments = buildCreationArguments {},
6064
init: suspend BuildingStateMachine.() -> Unit
61-
): StateMachine = StateMachineImpl(
62-
name,
63-
childMode,
64-
creationArguments,
65-
this,
66-
).apply {
67-
init()
68-
if (start) start()
65+
): StateMachine {
66+
contract {
67+
callsInPlace(init, InvocationKind.EXACTLY_ONCE)
68+
}
69+
return StateMachineImpl(
70+
name,
71+
childMode,
72+
creationArguments,
73+
this,
74+
).apply {
75+
init()
76+
if (start) start()
77+
}
6978
}

kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfo.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import ru.nsk.kstatemachine.event.Event
1111
import ru.nsk.kstatemachine.state.IState
1212
import ru.nsk.kstatemachine.state.RedirectPseudoState
1313
import ru.nsk.kstatemachine.transition.EventAndArgument
14+
import kotlin.contracts.ExperimentalContracts
15+
import kotlin.contracts.InvocationKind
16+
import kotlin.contracts.contract
1417

1518
/**
1619
* Hint to be used with [ExportMetaInfo]
@@ -123,5 +126,10 @@ private data class ExportMetaInfoBuilderImpl(
123126
}
124127
}
125128

126-
fun buildExportMetaInfo(builder: ExportMetaInfoBuilder.() -> Unit): ExportMetaInfo =
127-
ExportMetaInfoBuilderImpl().apply(builder).copy()
129+
@OptIn(ExperimentalContracts::class)
130+
fun buildExportMetaInfo(builder: ExportMetaInfoBuilder.() -> Unit): ExportMetaInfo {
131+
contract {
132+
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
133+
}
134+
return ExportMetaInfoBuilderImpl().apply(builder).copy()
135+
}

kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/MetaInfo.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ package ru.nsk.kstatemachine.metainfo
1010
import ru.nsk.kstatemachine.state.IState
1111
import ru.nsk.kstatemachine.transition.Transition
1212
import kotlin.collections.single
13+
import kotlin.contracts.ExperimentalContracts
14+
import kotlin.contracts.InvocationKind
15+
import kotlin.contracts.contract
1316

1417
/**
1518
* Additional static (designed to be immutable) info for library primitives like [IState] [Transition] etc.
@@ -57,8 +60,13 @@ private data class CompositeMetaInfoBuilderImpl(
5760
override var metaInfoSet: Set<MetaInfo> = emptySet()
5861
) : CompositeMetaInfoBuilder
5962

60-
fun buildCompositeMetaInfo(builder: CompositeMetaInfoBuilder.() -> Unit): CompositeMetaInfo =
61-
CompositeMetaInfoBuilderImpl().apply(builder).copy()
63+
@OptIn(ExperimentalContracts::class)
64+
fun buildCompositeMetaInfo(builder: CompositeMetaInfoBuilder.() -> Unit): CompositeMetaInfo {
65+
contract {
66+
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
67+
}
68+
return CompositeMetaInfoBuilderImpl().apply(builder).copy()
69+
}
6270

6371
fun buildCompositeMetaInfo(metaInfo1: MetaInfo, metaInfo2: MetaInfo, vararg infos: MetaInfo): CompositeMetaInfo =
6472
CompositeMetaInfoBuilderImpl(infos.toMutableSet().apply {

kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/UmlMetaInfo.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ package ru.nsk.kstatemachine.metainfo
99

1010
import ru.nsk.kstatemachine.state.IState
1111
import ru.nsk.kstatemachine.transition.Transition
12+
import kotlin.contracts.ExperimentalContracts
13+
import kotlin.contracts.InvocationKind
14+
import kotlin.contracts.contract
1215

1316
/**
1417
* Standard [MetaInfo], to control export PlantUML and Mermaid feature visualization.
@@ -53,5 +56,10 @@ private data class UmlMetaInfoBuilderImpl(
5356
override var umlNotes: List<String> = emptyList(),
5457
) : UmlMetaInfoBuilder
5558

56-
fun buildUmlMetaInfo(builder: UmlMetaInfoBuilder.() -> Unit): UmlMetaInfo =
57-
UmlMetaInfoBuilderImpl().apply(builder).copy()
59+
@OptIn(ExperimentalContracts::class)
60+
fun buildUmlMetaInfo(builder: UmlMetaInfoBuilder.() -> Unit): UmlMetaInfo {
61+
contract {
62+
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
63+
}
64+
return UmlMetaInfoBuilderImpl().apply(builder).copy()
65+
}

kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/state/IState.kt

Lines changed: 12 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,23 @@
55
* All rights reserved.
66
*/
77

8+
@file:OptIn(ExperimentalContracts::class)
9+
810
package ru.nsk.kstatemachine.state
911

10-
import ru.nsk.kstatemachine.event.DataExtractor
11-
import ru.nsk.kstatemachine.event.defaultDataExtractor
1212
import ru.nsk.kstatemachine.metainfo.MetaInfo
13-
import ru.nsk.kstatemachine.state.pseudo.DefaultChoiceDataState
14-
import ru.nsk.kstatemachine.state.pseudo.DefaultChoiceState
15-
import ru.nsk.kstatemachine.state.pseudo.DefaultHistoryState
1613
import ru.nsk.kstatemachine.statemachine.StateMachine
1714
import ru.nsk.kstatemachine.statemachine.StateMachineDslMarker
18-
import ru.nsk.kstatemachine.transition.EventAndArgument
1915
import ru.nsk.kstatemachine.transition.TransitionDirection
2016
import ru.nsk.kstatemachine.transition.TransitionDirectionProducerPolicy
2117
import ru.nsk.kstatemachine.transition.TransitionParams
2218
import ru.nsk.kstatemachine.visitors.CoVisitor
2319
import ru.nsk.kstatemachine.visitors.GetActiveStatesVisitor
2420
import ru.nsk.kstatemachine.visitors.Visitor
2521
import ru.nsk.kstatemachine.visitors.VisitorAcceptor
22+
import kotlin.contracts.ExperimentalContracts
23+
import kotlin.contracts.InvocationKind
24+
import kotlin.contracts.contract
2625
import kotlin.reflect.KClass
2726

2827
/**
@@ -41,6 +40,7 @@ interface IState : TransitionStateApi, VisitorAcceptor {
4140
val isFinished: Boolean
4241
val listeners: Collection<Listener>
4342
val childMode: ChildMode
43+
4444
/**
4545
* Might be changed only during machine setup.
4646
*/
@@ -91,9 +91,12 @@ interface IState : TransitionStateApi, VisitorAcceptor {
9191
}
9292
}
9393

94-
suspend fun <S : IState> IState.addState(state: S, init: StateBlock<S>? = null): S {
94+
suspend inline fun <S : IState> IState.addState(state: S, init: StateBlock<S>): S {
95+
contract {
96+
callsInPlace(init, InvocationKind.EXACTLY_ONCE)
97+
}
9598
addState(state)
96-
if (init != null) state.init()
99+
state.init()
97100
return state
98101
}
99102

@@ -238,104 +241,4 @@ inline fun <reified S : IState> IState.requireState(recursive: Boolean = true) =
238241

239242
suspend operator fun <S : IState> S.invoke(block: StateBlock<S>) = block()
240243

241-
fun IState.machineOrNull(): StateMachine? = if (this is StateMachine) this else parent?.machineOrNull()
242-
243-
/**
244-
* @param name is optional and is useful for getting state instance after state machine setup
245-
* with [IState.findState] and for debugging.
246-
*/
247-
suspend fun IState.state(
248-
name: String? = null,
249-
childMode: ChildMode = ChildMode.EXCLUSIVE,
250-
init: StateBlock<State>? = null
251-
) = addState(DefaultState(name, childMode), init)
252-
253-
suspend inline fun <reified D : Any> IState.dataState(
254-
name: String? = null,
255-
defaultData: D? = null,
256-
childMode: ChildMode = ChildMode.EXCLUSIVE,
257-
dataExtractor: DataExtractor<D> = defaultDataExtractor(),
258-
noinline init: StateBlock<DataState<D>>? = null
259-
) = addState(defaultDataState(name, defaultData, childMode, dataExtractor), init)
260-
261-
/**
262-
* A shortcut for [state] and [IState.setInitialState] calls
263-
*/
264-
suspend fun IState.initialState(
265-
name: String? = null,
266-
childMode: ChildMode = ChildMode.EXCLUSIVE,
267-
init: StateBlock<State>? = null
268-
) = addInitialState(DefaultState(name, childMode), init)
269-
270-
/**
271-
* @param defaultData is necessary for initial [DataState]
272-
*/
273-
suspend inline fun <reified D : Any> IState.initialDataState(
274-
name: String? = null,
275-
defaultData: D,
276-
childMode: ChildMode = ChildMode.EXCLUSIVE,
277-
dataExtractor: DataExtractor<D> = defaultDataExtractor(),
278-
noinline init: StateBlock<DataState<D>>? = null
279-
) = addInitialState(defaultDataState(name, defaultData, childMode, dataExtractor), init)
280-
281-
/**
282-
* A shortcut for [IState.addState] and [IState.setInitialState] calls
283-
*/
284-
suspend fun <S : IState> IState.addInitialState(state: S, init: StateBlock<S>? = null): S {
285-
addState(state, init)
286-
setInitialState(state)
287-
return state
288-
}
289-
290-
/**
291-
* Helper dsl method for adding final states. This is exactly the same as simply call [IState.addState] but makes
292-
* code more self expressive.
293-
*/
294-
suspend fun <S : IFinalState> IState.addFinalState(state: S, init: StateBlock<S>? = null) =
295-
addState(state, init)
296-
297-
suspend fun IState.finalState(name: String? = null, init: StateBlock<FinalState>? = null) =
298-
addFinalState(DefaultFinalState(name), init)
299-
300-
suspend fun IState.initialFinalState(name: String? = null, init: StateBlock<FinalState>? = null) =
301-
addInitialState(DefaultFinalState(name), init)
302-
303-
suspend inline fun <reified D : Any> IState.finalDataState(
304-
name: String? = null,
305-
defaultData: D? = null,
306-
dataExtractor: DataExtractor<D> = defaultDataExtractor(),
307-
noinline init: StateBlock<FinalDataState<D>>? = null
308-
) = addFinalState(defaultFinalDataState(name, defaultData, dataExtractor), init)
309-
310-
suspend inline fun <reified D : Any> IState.initialFinalDataState(
311-
name: String? = null,
312-
defaultData: D? = null,
313-
dataExtractor: DataExtractor<D> = defaultDataExtractor(),
314-
noinline init: StateBlock<FinalDataState<D>>? = null
315-
) = addInitialState(defaultFinalDataState(name, defaultData, dataExtractor), init)
316-
317-
fun IState.choiceState(
318-
name: String? = null,
319-
choiceAction: suspend EventAndArgument<*>.() -> State
320-
) = addState(DefaultChoiceState(name, choiceAction = choiceAction))
321-
322-
suspend fun IState.initialChoiceState(
323-
name: String? = null,
324-
choiceAction: suspend EventAndArgument<*>.() -> State
325-
) = addInitialState(DefaultChoiceState(name, choiceAction = choiceAction))
326-
327-
inline fun <reified D : Any> IState.choiceDataState(
328-
name: String? = null,
329-
noinline choiceAction: suspend EventAndArgument<*>.() -> DataState<D>
330-
) = addState(DefaultChoiceDataState(name, D::class, choiceAction = choiceAction))
331-
332-
suspend inline fun <reified D : Any> IState.initialChoiceDataState(
333-
name: String? = null,
334-
noinline choiceAction: suspend EventAndArgument<*>.() -> DataState<D>
335-
) = addInitialState(DefaultChoiceDataState(name, D::class, choiceAction = choiceAction))
336-
337-
fun IState.historyState(
338-
name: String? = null,
339-
defaultState: IState? = null,
340-
historyType: HistoryType = HistoryType.SHALLOW,
341-
) = addState(DefaultHistoryState(name, defaultState, historyType))
244+
fun IState.machineOrNull(): StateMachine? = this as? StateMachine ?: parent?.machineOrNull()

0 commit comments

Comments
 (0)