| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library barback.transform_node; | 5 library barback.transform_node; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'asset.dart'; | 9 import 'asset.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| (...skipping 27 matching lines...) Expand all Loading... |
| 38 /// A string describing the location of [this] in the transformer graph. | 38 /// A string describing the location of [this] in the transformer graph. |
| 39 final String _location; | 39 final String _location; |
| 40 | 40 |
| 41 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | 41 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. |
| 42 StreamSubscription _primarySubscription; | 42 StreamSubscription _primarySubscription; |
| 43 | 43 |
| 44 /// The subscription to [phase]'s [Phase.onAsset] stream. | 44 /// The subscription to [phase]'s [Phase.onAsset] stream. |
| 45 StreamSubscription<AssetNode> _phaseSubscription; | 45 StreamSubscription<AssetNode> _phaseSubscription; |
| 46 | 46 |
| 47 /// Whether [this] is dirty and still has more processing to do. | 47 /// Whether [this] is dirty and still has more processing to do. |
| 48 bool get isDirty => _state != _State.NOT_PRIMARY && _state != _State.APPLIED; | 48 bool get isDirty => _state != _State.NOT_PRIMARY && |
| 49 _state != _State.APPLIED && _state != _State.DECLARED; |
| 49 | 50 |
| 50 /// Whether this transform is lazy and this transform has yet to be forced. | 51 /// Whether this transform is deferred. |
| 51 /// | 52 /// |
| 52 /// A transform being lazy is distinct from a transformer being lazy. A | 53 /// A transform is deferred if either its transformer is lazy or if its |
| 53 /// transformer that's declaring but not lazy will have lazy transforms for | 54 /// transformer is declaring and its primary input comes from a deferred |
| 54 /// primary inputs that are themselves lazy. | 55 /// transformer. |
| 55 bool _isLazy; | 56 final bool deferred; |
| 57 |
| 58 /// Whether this is a deferred transform waiting for [force] to be called to |
| 59 /// generate inputs. |
| 60 /// |
| 61 /// This defaults to `true` for deferred transforms and `false` otherwise. |
| 62 /// During or after running `isPrimary` or `declareOutputs`, this may become |
| 63 /// `false`, indicating that the transform has been forced and should generate |
| 64 /// outputs as soon as possible. It will only be set back to `true` if an |
| 65 /// input changes *after* `apply` has completed. |
| 66 bool _awaitingForce; |
| 56 | 67 |
| 57 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | 68 /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
| 58 final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | 69 final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); |
| 59 | 70 |
| 60 /// The controllers for the asset nodes emitted by this node. | 71 /// The controllers for the asset nodes emitted by this node. |
| 61 final _outputControllers = new Map<AssetId, AssetNodeController>(); | 72 final _outputControllers = new Map<AssetId, AssetNodeController>(); |
| 62 | 73 |
| 63 /// The ids of inputs the transformer tried and failed to read last time it | 74 /// The ids of inputs the transformer tried and failed to read last time it |
| 64 /// ran. | 75 /// ran. |
| 65 final _missingInputs = new Set<AssetId>(); | 76 final _missingInputs = new Set<AssetId>(); |
| (...skipping 27 matching lines...) Expand all Loading... |
| 93 /// This is synchronous because error logs can cause the transform to fail, so | 104 /// This is synchronous because error logs can cause the transform to fail, so |
| 94 /// we need to ensure that their processing isn't delayed until after the | 105 /// we need to ensure that their processing isn't delayed until after the |
| 95 /// transform or build has finished. | 106 /// transform or build has finished. |
| 96 Stream<LogEntry> get onLog => _onLogPool.stream; | 107 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 97 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | 108 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 98 | 109 |
| 99 /// A controller for log entries emitted by this node. | 110 /// A controller for log entries emitted by this node. |
| 100 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); | 111 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); |
| 101 | 112 |
| 102 /// The current state of [this]. | 113 /// The current state of [this]. |
| 103 var _state = _State.COMPUTING_IS_PRIMARY; | 114 var _state = _State.DECLARING; |
| 104 | 115 |
| 105 /// Whether [this] has been marked as removed. | 116 /// Whether [this] has been marked as removed. |
| 106 bool get _isRemoved => _onAssetController.isClosed; | 117 bool get _isRemoved => _onAssetController.isClosed; |
| 107 | 118 |
| 108 /// Whether the most recent run of this transform has declared that it | 119 /// Whether the most recent run of this transform has declared that it |
| 109 /// consumes the primary input. | 120 /// consumes the primary input. |
| 110 /// | 121 /// |
| 111 /// Defaults to `false`. This is not meaningful unless [_state] is | 122 /// Defaults to `false`. This is not meaningful unless [_state] is |
| 112 /// [_State.APPLIED]. | 123 /// [_State.APPLIED] or [_State.DECLARED]. |
| 113 bool _consumePrimary = false; | 124 bool _consumePrimary = false; |
| 114 | 125 |
| 115 /// The set of output ids that [transformer] declared it would emit. | 126 /// The set of output ids that [transformer] declared it would emit. |
| 116 /// | 127 /// |
| 117 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its | 128 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its |
| 118 /// [declareOutputs] has been run successfully. | 129 /// [declareOutputs] has been run successfully. |
| 119 Set<AssetId> _declaredOutputs; | 130 Set<AssetId> _declaredOutputs; |
| 120 | 131 |
| 121 TransformNode(this.phase, Transformer transformer, AssetNode primary, | 132 TransformNode(this.phase, Transformer transformer, AssetNode primary, |
| 122 this._location) | 133 this._location) |
| 123 : transformer = transformer, | 134 : transformer = transformer, |
| 124 primary = primary, | 135 primary = primary, |
| 125 _isLazy = transformer is LazyTransformer || | 136 deferred = transformer is LazyTransformer || |
| 126 (transformer is DeclaringTransformer && primary.isLazy) { | 137 (transformer is DeclaringTransformer && primary.deferred) { |
| 138 _awaitingForce = deferred; |
| 139 |
| 127 _onLogPool.add(_onLogController.stream); | 140 _onLogPool.add(_onLogController.stream); |
| 128 | 141 |
| 129 if (!_isLazy) primary.force(); | 142 if (!deferred) primary.force(); |
| 130 | 143 |
| 131 _primarySubscription = primary.onStateChange.listen((state) { | 144 _primarySubscription = primary.onStateChange.listen((state) { |
| 132 if (state.isRemoved) { | 145 if (state.isRemoved) { |
| 133 remove(); | 146 remove(); |
| 134 } else { | 147 } else { |
| 148 if (state.isDirty && !deferred) primary.force(); |
| 135 _dirty(); | 149 _dirty(); |
| 136 } | 150 } |
| 137 }); | 151 }); |
| 138 | 152 |
| 139 _phaseSubscription = phase.previous.onAsset.listen((node) { | 153 _phaseSubscription = phase.previous.onAsset.listen((node) { |
| 140 if (_missingInputs.contains(node.id)) _dirty(); | 154 if (!_missingInputs.contains(node.id)) return; |
| 155 if (!deferred) node.force(); |
| 156 _dirty(); |
| 141 }); | 157 }); |
| 142 | 158 |
| 143 _isPrimary(); | 159 _isPrimary(); |
| 144 } | 160 } |
| 145 | 161 |
| 146 /// The [TransformInfo] describing this node. | 162 /// The [TransformInfo] describing this node. |
| 147 /// | 163 /// |
| 148 /// [TransformInfo] is the publicly-visible representation of a transform | 164 /// [TransformInfo] is the publicly-visible representation of a transform |
| 149 /// node. | 165 /// node. |
| 150 TransformInfo get info => new TransformInfo(transformer, primary.id); | 166 TransformInfo get info => new TransformInfo(transformer, primary.id); |
| (...skipping 11 matching lines...) Expand all Loading... |
| 162 _primarySubscription.cancel(); | 178 _primarySubscription.cancel(); |
| 163 _phaseSubscription.cancel(); | 179 _phaseSubscription.cancel(); |
| 164 _clearInputSubscriptions(); | 180 _clearInputSubscriptions(); |
| 165 _clearOutputs(); | 181 _clearOutputs(); |
| 166 if (_passThroughController != null) { | 182 if (_passThroughController != null) { |
| 167 _passThroughController.setRemoved(); | 183 _passThroughController.setRemoved(); |
| 168 _passThroughController = null; | 184 _passThroughController = null; |
| 169 } | 185 } |
| 170 } | 186 } |
| 171 | 187 |
| 172 /// If [transformer] is lazy, ensures that its concrete outputs will be | 188 /// If [this] is deferred, ensures that its concrete outputs will be |
| 173 /// generated. | 189 /// generated. |
| 174 void force() { | 190 void force() { |
| 175 // TODO(nweiz): we might want to have a timeout after which, if the | 191 if (!_awaitingForce) return; |
| 176 // transform's outputs have gone unused, we switch it back to lazy mode. | |
| 177 if (!_isLazy) return; | |
| 178 primary.force(); | 192 primary.force(); |
| 179 _isLazy = false; | 193 _awaitingForce = false; |
| 180 _dirty(); | 194 _dirty(); |
| 181 } | 195 } |
| 182 | 196 |
| 183 /// Marks this transform as dirty. | 197 /// Marks this transform as dirty. |
| 184 /// | 198 /// |
| 185 /// This causes all of the transform's outputs to be marked as dirty as well. | 199 /// This causes all of the transform's outputs to be marked as dirty as well. |
| 186 void _dirty() { | 200 void _dirty() { |
| 187 if (_state == _State.NOT_PRIMARY) { | 201 if (_state == _State.NOT_PRIMARY) { |
| 188 _emitPassThrough(); | 202 _emitPassThrough(); |
| 189 return; | 203 return; |
| 190 } | 204 } |
| 191 if (_state == _State.COMPUTING_IS_PRIMARY || _isLazy) return; | 205 |
| 206 // If we're in the process of running [isPrimary] or [declareOutputs], we |
| 207 // already know that [apply] needs to be run so there's nothing we need to |
| 208 // mark as dirty. |
| 209 if (_state == _State.DECLARING) return; |
| 210 |
| 211 // If we're waiting until [force] is called to run [apply], we don't want to |
| 212 // run [apply] too early. |
| 213 if (_awaitingForce) return; |
| 214 |
| 215 if (_state == _State.APPLIED && deferred) { |
| 216 // Transition to DECLARED, indicating that we know what outputs [apply] |
| 217 // will emit but we're waiting to emit them concretely until [force] is |
| 218 // called. |
| 219 _state = _State.DECLARED; |
| 220 _awaitingForce = true; |
| 221 for (var controller in _outputControllers.values) { |
| 222 controller.setLazy(force); |
| 223 } |
| 224 return; |
| 225 } |
| 192 | 226 |
| 193 if (_passThroughController != null) _passThroughController.setDirty(); | 227 if (_passThroughController != null) _passThroughController.setDirty(); |
| 194 for (var controller in _outputControllers.values) { | 228 for (var controller in _outputControllers.values) { |
| 195 controller.setDirty(); | 229 controller.setDirty(); |
| 196 } | 230 } |
| 197 | 231 |
| 198 if (_state == _State.APPLIED) { | 232 if (_state == _State.APPLIED || _state == _State.DECLARED) { |
| 199 _apply(); | 233 _apply(); |
| 200 } else { | 234 } else { |
| 201 _state = _State.NEEDS_APPLY; | 235 _state = _State.NEEDS_APPLY; |
| 202 } | 236 } |
| 203 } | 237 } |
| 204 | 238 |
| 205 /// Runs [transformer.isPrimary] and adjusts [this]'s state according to the | 239 /// Runs [transformer.isPrimary] and adjusts [this]'s state according to the |
| 206 /// result. | 240 /// result. |
| 207 /// | 241 /// |
| 208 /// This will also run [_declareOutputs] and/or [_apply] as appropriate. | 242 /// This will also run [_declareOutputs] and/or [_apply] as appropriate. |
| 209 void _isPrimary() { | 243 void _isPrimary() { |
| 210 syncFuture(() => transformer.isPrimary(primary.id)) | 244 syncFuture(() => transformer.isPrimary(primary.id)) |
| 211 .catchError((error, stackTrace) { | 245 .catchError((error, stackTrace) { |
| 212 if (_isRemoved) return false; | 246 if (_isRemoved) return false; |
| 213 | 247 |
| 214 // Catch all transformer errors and pipe them to the results stream. This | 248 // Catch all transformer errors and pipe them to the results stream. This |
| 215 // is so a broken transformer doesn't take down the whole graph. | 249 // is so a broken transformer doesn't take down the whole graph. |
| 216 phase.cascade.reportError(_wrapException(error, stackTrace)); | 250 phase.cascade.reportError(_wrapException(error, stackTrace)); |
| 217 | 251 |
| 218 return false; | 252 return false; |
| 219 }).then((isPrimary) { | 253 }).then((isPrimary) { |
| 220 if (_isRemoved) return null; | 254 if (_isRemoved) return null; |
| 221 if (isPrimary) { | 255 if (isPrimary) { |
| 222 return _declareOutputs().then((_) { | 256 return _declareOutputs().then((_) { |
| 223 if (_isRemoved) return; | 257 if (_isRemoved) return; |
| 224 if (_isLazy) { | 258 if (_awaitingForce) { |
| 225 _state = _State.APPLIED; | 259 _state = _State.DECLARED; |
| 226 _onDoneController.add(null); | 260 _onDoneController.add(null); |
| 227 } else { | 261 } else { |
| 228 _apply(); | 262 _apply(); |
| 229 } | 263 } |
| 230 }); | 264 }); |
| 231 } | 265 } |
| 232 | 266 |
| 233 _emitPassThrough(); | 267 _emitPassThrough(); |
| 234 _state = _State.NOT_PRIMARY; | 268 _state = _State.NOT_PRIMARY; |
| 235 _onDoneController.add(null); | 269 _onDoneController.add(null); |
| (...skipping 19 matching lines...) Expand all Loading... |
| 255 .where((id) => id.package != phase.cascade.package).toSet(); | 289 .where((id) => id.package != phase.cascade.package).toSet(); |
| 256 for (var id in invalidIds) { | 290 for (var id in invalidIds) { |
| 257 _declaredOutputs.remove(id); | 291 _declaredOutputs.remove(id); |
| 258 // TODO(nweiz): report this as a warning rather than a failing error. | 292 // TODO(nweiz): report this as a warning rather than a failing error. |
| 259 phase.cascade.reportError(new InvalidOutputException(info, id)); | 293 phase.cascade.reportError(new InvalidOutputException(info, id)); |
| 260 } | 294 } |
| 261 | 295 |
| 262 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); | 296 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); |
| 263 | 297 |
| 264 for (var id in _declaredOutputs) { | 298 for (var id in _declaredOutputs) { |
| 265 var controller = _isLazy | 299 var controller = _awaitingForce |
| 266 ? new AssetNodeController.lazy(id, force, this) | 300 ? new AssetNodeController.lazy(id, force, this) |
| 267 : new AssetNodeController(id, this); | 301 : new AssetNodeController(id, this); |
| 268 _outputControllers[id] = controller; | 302 _outputControllers[id] = controller; |
| 269 _onAssetController.add(controller.node); | 303 _onAssetController.add(controller.node); |
| 270 } | 304 } |
| 271 }).catchError((error, stackTrace) { | 305 }).catchError((error, stackTrace) { |
| 272 if (_isRemoved) return; | 306 if (_isRemoved) return; |
| 273 phase.cascade.reportError(_wrapException(error, stackTrace)); | 307 phase.cascade.reportError(_wrapException(error, stackTrace)); |
| 274 }); | 308 }); |
| 275 } | 309 } |
| 276 | 310 |
| 277 /// Applies this transform. | 311 /// Applies this transform. |
| 278 void _apply() { | 312 void _apply() { |
| 279 assert(!_isRemoved && !_isLazy); | 313 assert(!_isRemoved && !_awaitingForce); |
| 280 | 314 |
| 281 // Clear input subscriptions here as well as in [_process] because [_apply] | 315 // Clear input subscriptions here as well as in [_process] because [_apply] |
| 282 // may be restarted independently if only a secondary input changes. | 316 // may be restarted independently if only a secondary input changes. |
| 283 _clearInputSubscriptions(); | 317 _clearInputSubscriptions(); |
| 284 _state = _State.APPLYING; | 318 _state = _State.APPLYING; |
| 285 _runApply().then((hadError) { | 319 _runApply().then((hadError) { |
| 286 if (_isRemoved) return; | 320 if (_isRemoved) return; |
| 287 | 321 |
| 288 if (_state == _State.NEEDS_APPLY) { | 322 if (_state == _State.NEEDS_APPLY) { |
| 289 _apply(); | 323 _apply(); |
| (...skipping 27 matching lines...) Expand all Loading... |
| 317 return phase.previous.getOutput(id).then((node) { | 351 return phase.previous.getOutput(id).then((node) { |
| 318 // Throw if the input isn't found. This ensures the transformer's apply | 352 // Throw if the input isn't found. This ensures the transformer's apply |
| 319 // is exited. We'll then catch this and report it through the proper | 353 // is exited. We'll then catch this and report it through the proper |
| 320 // results stream. | 354 // results stream. |
| 321 if (node == null) { | 355 if (node == null) { |
| 322 _missingInputs.add(id); | 356 _missingInputs.add(id); |
| 323 throw new AssetNotFoundException(id); | 357 throw new AssetNotFoundException(id); |
| 324 } | 358 } |
| 325 | 359 |
| 326 _inputSubscriptions.putIfAbsent(node.id, () { | 360 _inputSubscriptions.putIfAbsent(node.id, () { |
| 327 return node.onStateChange.listen((_) => _dirty()); | 361 return node.onStateChange.listen((state) => _dirty()); |
| 328 }); | 362 }); |
| 329 | 363 |
| 330 return node.asset; | 364 return node.asset; |
| 331 }); | 365 }); |
| 332 } | 366 } |
| 333 | 367 |
| 334 /// Run [Transformer.apply] as soon as [primary] is available. | 368 /// Run [Transformer.apply] as soon as [primary] is available. |
| 335 /// | 369 /// |
| 336 /// Returns whether or not an error occurred while running the transformer. | 370 /// Returns whether or not an error occurred while running the transformer. |
| 337 Future<bool> _runApply() { | 371 Future<bool> _runApply() { |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 468 _onLogController.add( | 502 _onLogController.add( |
| 469 new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); | 503 new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); |
| 470 } | 504 } |
| 471 | 505 |
| 472 String toString() => | 506 String toString() => |
| 473 "transform node in $_location for $transformer on $primary"; | 507 "transform node in $_location for $transformer on $primary"; |
| 474 } | 508 } |
| 475 | 509 |
| 476 /// The enum of states that [TransformNode] can be in. | 510 /// The enum of states that [TransformNode] can be in. |
| 477 class _State { | 511 class _State { |
| 478 /// The transform is running [Transformer.isPrimary]. | 512 /// The transform is running [Transformer.isPrimary] followed by |
| 513 /// [DeclaringTransformer.declareOutputs] (for a [DeclaringTransformer]). |
| 479 /// | 514 /// |
| 480 /// This is the initial state of the transformer. Once [Transformer.isPrimary] | 515 /// This is the initial state of the transformer, and it will only occur once |
| 481 /// finishes running, this will transition to [APPLYING] if the input is | 516 /// since [Transformer.isPrimary] and [DeclaringTransformer.declareOutputs] |
| 482 /// primary, or [NOT_PRIMARY] if it's not. | 517 /// are independent of the contents of the primary input. Once the two methods |
| 483 static final COMPUTING_IS_PRIMARY = const _State._("computing isPrimary"); | 518 /// finish running, this will transition to [NOT_PRIMARY] if the input isn't |
| 519 /// primary, [DECLARED] if the transform is deferred, and [APPLYING] |
| 520 /// otherwise. |
| 521 static final DECLARING = const _State._("computing isPrimary"); |
| 522 |
| 523 /// The transform is deferred and has run |
| 524 /// [DeclaringTransformer.declareOutputs] but hasn't yet been forced. |
| 525 /// |
| 526 /// This will transition to [APPLYING] when one of the outputs has been |
| 527 /// forced. |
| 528 static final DECLARED = const _State._("declared"); |
| 484 | 529 |
| 485 /// The transform is running [Transformer.apply]. | 530 /// The transform is running [Transformer.apply]. |
| 486 /// | 531 /// |
| 487 /// If an input changes while in this state, it will transition to | 532 /// If an input changes while in this state, it will transition to |
| 488 /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when | 533 /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when |
| 489 /// [Transformer.apply] finishes running, it will transition to [APPLIED]. | 534 /// [Transformer.apply] finishes running, it will transition to [APPLIED]. |
| 490 static final APPLYING = const _State._("applying"); | 535 static final APPLYING = const _State._("applying"); |
| 491 | 536 |
| 492 /// The transform is running [Transformer.apply], but an input changed after | 537 /// The transform is running [Transformer.apply], but an input changed after |
| 493 /// it started, so it will need to re-run [Transformer.apply]. | 538 /// it started, so it will need to re-run [Transformer.apply]. |
| 494 /// | 539 /// |
| 495 /// This will transition to [APPLYING] once [Transformer.apply] finishes | 540 /// This will transition to [APPLYING] once [Transformer.apply] finishes |
| 496 /// running. | 541 /// running. |
| 497 static final NEEDS_APPLY = const _State._("needs apply"); | 542 static final NEEDS_APPLY = const _State._("needs apply"); |
| 498 | 543 |
| 499 /// The transform has finished running [Transformer.apply], whether or not it | 544 /// The transform has finished running [Transformer.apply], whether or not it |
| 500 /// emitted an error. | 545 /// emitted an error. |
| 501 /// | 546 /// |
| 502 /// If the transformer is lazy, the [TransformNode] can also be in this state | 547 /// If the transformer is deferred, the [TransformNode] can also be in this |
| 503 /// when [Transformer.declareOutputs] has been run but [Transformer.apply] has | 548 /// state when [Transformer.declareOutputs] has been run but |
| 504 /// not. | 549 /// [Transformer.apply] has not. |
| 505 /// | 550 /// |
| 506 /// If an input changes, this will transition to [APPLYING]. | 551 /// If an input changes, this will transition to [DECLARED] if the transform |
| 552 /// is deferred and [APPLYING] otherwise. |
| 507 static final APPLIED = const _State._("applied"); | 553 static final APPLIED = const _State._("applied"); |
| 508 | 554 |
| 509 /// The transform has finished running [Transformer.isPrimary], which returned | 555 /// The transform has finished running [Transformer.isPrimary], which returned |
| 510 /// `false`. | 556 /// `false`. |
| 511 /// | 557 /// |
| 512 /// This will never transition to another state. | 558 /// This will never transition to another state. |
| 513 static final NOT_PRIMARY = const _State._("not primary"); | 559 static final NOT_PRIMARY = const _State._("not primary"); |
| 514 | 560 |
| 515 final String name; | 561 final String name; |
| 516 | 562 |
| 517 const _State._(this.name); | 563 const _State._(this.name); |
| 518 | 564 |
| 519 String toString() => name; | 565 String toString() => name; |
| 520 } | 566 } |
| OLD | NEW |