| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of '../fragment.dart'; |
| |
| /// Strategy pattern for creating different encodings of a declared field. |
| /// |
| /// This is used to provide lowerings for late fields using synthesized getters |
| /// and setters. |
| sealed class FieldEncoding { |
| /// Creates the members necessary for this field encoding. |
| /// |
| /// This method is called for both outline and full compilation so the created |
| /// members should be without body. The member bodies are created through |
| /// [createBodies]. |
| void buildOutlineNode( |
| SourceLibraryBuilder libraryBuilder, |
| NameScheme nameScheme, |
| PropertyReferences references, { |
| required bool isAbstractOrExternal, |
| required List<TypeParameter>? classTypeParameters, |
| }); |
| |
| /// Calls [f] for each member needed for this field encoding. |
| void registerMembers(BuildNodesCallback f); |
| |
| /// Creates the bodies needed for the field encoding using [initializer] as |
| /// the declared initializer expression. |
| /// |
| /// This method is not called for fields in outlines unless their are constant |
| /// or part of a const constructor. |
| void createBodies(CoreTypes coreTypes, Expression? initializer); |
| |
| /// The type of the declared field. |
| abstract DartType type; |
| |
| /// Builds the [Initializer]s for each field used to encode this field |
| /// using the [fileOffset] for the created nodes and [value] as the initial |
| /// field value. |
| /// |
| /// This is only used for instance fields. |
| List<Initializer> createInitializer( |
| int fileOffset, |
| Expression value, { |
| required bool isSynthetic, |
| }); |
| |
| /// Creates the AST node for this field as the default initializer. |
| /// |
| /// This is only used for instance fields. |
| void buildImplicitDefaultValue(); |
| |
| /// Creates the [Initializer] for the implicit initialization of this field |
| /// in a constructor. |
| /// |
| /// This is only used for instance fields. |
| Initializer buildImplicitInitializer(); |
| |
| /// Creates the [Initializer] for the invalid initialization of this field. |
| /// |
| /// This is only used for instance fields. |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }); |
| |
| /// Registers that the (implicit) setter associated with this field needs to |
| /// contain a runtime type check to deal with generic covariance. |
| void setCovariantByClass(); |
| |
| /// Returns the field that holds the field value at runtime. |
| Field get field; |
| |
| /// The [Member] built during [FieldDeclaration.buildFieldOutlineExpressions]. |
| Member get builtMember; |
| |
| /// Returns the members that holds the field annotations. |
| Iterable<Annotatable> get annotatables; |
| |
| /// Returns the member used to read the field value. |
| Member get readTarget; |
| |
| /// Returns the reference used to read the field value. |
| Reference get readTargetReference; |
| |
| /// Returns the member used to write to the field. |
| Member? get writeTarget; |
| |
| /// Returns the reference used to write to the field. |
| Reference? get writeTargetReference; |
| |
| /// Returns the references to the generated members that are visible through |
| /// exports. |
| /// |
| /// This is the getter reference, and, if available, the setter reference. |
| Iterable<Reference> get exportedReferenceMembers; |
| |
| /// Returns a list of the field, getters and methods created by this field |
| /// encoding. |
| List<ClassMember> get localMembers; |
| |
| /// Returns a list of the setters created by this field encoding. |
| List<ClassMember> get localSetters; |
| |
| /// Registers that a `super` call has occurred in the initializer of this |
| /// field. |
| void registerSuperCall(); |
| } |
| |
| class RegularFieldEncoding implements FieldEncoding { |
| final FieldFragment _fragment; |
| final bool isEnumElement; |
| Field? _field; |
| DartType _type = const DynamicType(); |
| |
| RegularFieldEncoding(this._fragment, {required this.isEnumElement}) {} |
| |
| @override |
| DartType get type => _type; |
| |
| @override |
| void set type(DartType value) { |
| _type = value; |
| _field?.type = value; |
| } |
| |
| @override |
| void createBodies(CoreTypes coreTypes, Expression? initializer) { |
| if (initializer != null) { |
| _field!.initializer = initializer..parent = _field; |
| } |
| } |
| |
| @override |
| List<Initializer> createInitializer( |
| int fileOffset, |
| Expression value, { |
| required bool isSynthetic, |
| }) { |
| return <Initializer>[ |
| new FieldInitializer(_field!, value) |
| ..fileOffset = fileOffset |
| ..isSynthetic = isSynthetic, |
| ]; |
| } |
| |
| @override |
| void buildOutlineNode( |
| SourceLibraryBuilder libraryBuilder, |
| NameScheme nameScheme, |
| PropertyReferences references, { |
| required bool isAbstractOrExternal, |
| required List<TypeParameter>? classTypeParameters, |
| }) { |
| bool isImmutable = _fragment.modifiers.isLate |
| ? (_fragment.modifiers.isFinal && _fragment.modifiers.hasInitializer) |
| : (_fragment.modifiers.isFinal || _fragment.modifiers.isConst); |
| _field = isImmutable |
| ? new Field.immutable( |
| dummyName, |
| type: _type, |
| isFinal: _fragment.modifiers.isFinal, |
| isConst: _fragment.modifiers.isConst, |
| isLate: _fragment.modifiers.isLate, |
| fileUri: _fragment.fileUri, |
| fieldReference: references.fieldReference, |
| getterReference: references.getterReference, |
| isEnumElement: isEnumElement, |
| ) |
| : new Field.mutable( |
| dummyName, |
| type: _type, |
| isFinal: _fragment.modifiers.isFinal, |
| isLate: _fragment.modifiers.isLate, |
| fileUri: _fragment.fileUri, |
| fieldReference: references.fieldReference, |
| getterReference: references.getterReference, |
| setterReference: references.setterReference, |
| ); |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.Field, |
| _fragment.name, |
| isSynthesized: false, |
| ) |
| .attachMember(_field!); |
| _field! |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset; |
| _field!..isCovariantByDeclaration = _fragment.modifiers.isCovariant; |
| if (_fragment.builder.isExtensionMember) { |
| _field! |
| ..isStatic = true |
| ..isExtensionMember = true; |
| } else if (_fragment.builder.isExtensionTypeMember) { |
| _field! |
| ..isStatic = _fragment.builder.isStatic |
| ..isExtensionTypeMember = true; |
| } else { |
| bool isInstanceMember = |
| !_fragment.builder.isStatic && !_fragment.builder.isTopLevel; |
| _field! |
| ..isStatic = !isInstanceMember |
| ..isExtensionMember = false; |
| } |
| _field!.isLate = _fragment.modifiers.isLate; |
| } |
| |
| @override |
| void registerMembers(BuildNodesCallback f) { |
| f( |
| member: _field!, |
| kind: |
| _fragment.builder.isExtensionMember || |
| _fragment.builder.isExtensionTypeMember |
| ? BuiltMemberKind.ExtensionField |
| : BuiltMemberKind.Field, |
| ); |
| } |
| |
| @override |
| void setCovariantByClass() { |
| if (_field!.hasSetter) { |
| _field!.isCovariantByClass = true; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Field get field => _field!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Member get builtMember => _field!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Annotatable> get annotatables => [_field!]; |
| |
| @override |
| Member get readTarget => _field!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference get readTargetReference => _field!.getterReference; |
| |
| @override |
| Member get writeTarget => _field!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference? get writeTargetReference => _field!.setterReference; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Reference> get exportedReferenceMembers => [ |
| _field!.getterReference, |
| if (_field!.hasSetter) _field!.setterReference!, |
| ]; |
| |
| @override |
| List<ClassMember> get localMembers => <ClassMember>[ |
| new _FieldClassMember(_fragment.builder, _fragment, forSetter: false), |
| ]; |
| |
| @override |
| List<ClassMember> get localSetters => _fragment.hasSetter |
| ? [new _FieldClassMember(_fragment.builder, _fragment, forSetter: true)] |
| : const []; |
| |
| @override |
| void buildImplicitDefaultValue() { |
| _field!.initializer = new NullLiteral()..parent = _field; |
| } |
| |
| @override |
| Initializer buildImplicitInitializer() { |
| return new FieldInitializer(_field!, new NullLiteral())..isSynthetic = true; |
| } |
| |
| @override |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }) { |
| return new ShadowInvalidFieldInitializer(type, value, effect) |
| ..fileOffset = fileOffset; |
| } |
| |
| @override |
| void registerSuperCall() { |
| _field!.transformerFlags |= TransformerFlag.superCalls; |
| } |
| } |
| |
| abstract class AbstractLateFieldEncoding implements FieldEncoding { |
| final FieldFragment _fragment; |
| DartType? _type; |
| Field? _field; |
| Field? _lateIsSetField; |
| Procedure? _lateGetter; |
| Procedure? _lateSetter; |
| |
| // If `true`, an isSet field is used even when the type of the field is |
| // not potentially nullable. |
| // |
| // This is used to force use isSet fields in mixed mode encoding since |
| // we cannot trust non-nullable fields to be initialized with non-null values. |
| final late_lowering.IsSetStrategy _isSetStrategy; |
| late_lowering.IsSetEncoding? _isSetEncoding; |
| |
| // If `true`, the is-set field was register before the type was known to be |
| // nullable or non-nullable. In this case we do not try to remove it from |
| // the generated AST to avoid inconsistency between the class hierarchy used |
| // during and after inference. |
| // |
| // This is also used to force use isSet fields in mixed mode encoding since |
| // we cannot trust non-nullable fields to be initialized with non-null values. |
| bool _forceIncludeIsSetField; |
| |
| AbstractLateFieldEncoding( |
| this._fragment, { |
| required late_lowering.IsSetStrategy isSetStrategy, |
| }) : _isSetStrategy = isSetStrategy, |
| _forceIncludeIsSetField = |
| isSetStrategy == late_lowering.IsSetStrategy.forceUseIsSetField {} |
| |
| late_lowering.IsSetEncoding get isSetEncoding { |
| assert( |
| _type != null, |
| "Type has not been computed for field ${_fragment.name}.", |
| ); |
| return _isSetEncoding ??= late_lowering.computeIsSetEncoding( |
| _type!, |
| _isSetStrategy, |
| ); |
| } |
| |
| @override |
| void createBodies(CoreTypes coreTypes, Expression? initializer) { |
| assert( |
| _type != null, |
| "Type has not been computed for field ${_fragment.name}.", |
| ); |
| if (isSetEncoding == late_lowering.IsSetEncoding.useSentinel) { |
| _field!.initializer = |
| new StaticInvocation( |
| coreTypes.createSentinelMethod, |
| new Arguments([], types: [_type!]) |
| ..fileOffset = _fragment.nameOffset, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..parent = _field; |
| } else { |
| _field!.initializer = new NullLiteral() |
| ..fileOffset = _fragment.nameOffset |
| ..parent = _field; |
| } |
| if (_lateIsSetField != null) { |
| _lateIsSetField!.initializer = new BoolLiteral(false) |
| ..fileOffset = _fragment.nameOffset |
| ..parent = _lateIsSetField; |
| } |
| _lateGetter!.function.body = _createGetterBody( |
| coreTypes, |
| _fragment.name, |
| initializer, |
| )..parent = _lateGetter!.function; |
| // The initializer is copied from [_field] to [_lateGetter] so we copy the |
| // transformer flags to reflect whether the getter contains super calls. |
| _lateGetter!.transformerFlags = _field!.transformerFlags; |
| |
| if (_lateSetter != null) { |
| _lateSetter!.function.body = _createSetterBody( |
| coreTypes, |
| _fragment.name, |
| _lateSetter!.function.positionalParameters.first, |
| )..parent = _lateSetter!.function; |
| } |
| } |
| |
| @override |
| List<Initializer> createInitializer( |
| int fileOffset, |
| Expression value, { |
| required bool isSynthetic, |
| }) { |
| List<Initializer> initializers = <Initializer>[]; |
| if (_lateIsSetField != null) { |
| initializers.add( |
| new FieldInitializer( |
| _lateIsSetField!, |
| new BoolLiteral(true)..fileOffset = fileOffset, |
| ) |
| ..fileOffset = fileOffset |
| ..isSynthetic = isSynthetic, |
| ); |
| } |
| initializers.add( |
| new FieldInitializer(_field!, value) |
| ..fileOffset = fileOffset |
| ..isSynthetic = isSynthetic, |
| ); |
| return initializers; |
| } |
| |
| /// Creates an [Expression] that reads [_field]. |
| /// |
| /// If [needsPromotion] is `true`, the field will be read through a `let` |
| /// expression that promotes the expression to [_type]. This is needed for a |
| /// sound encoding of fields with type parameter type of undetermined |
| /// nullability. |
| Expression _createFieldRead({bool needsPromotion = false}) { |
| assert( |
| _type != null, |
| "Type has not been computed for field ${_fragment.name}.", |
| ); |
| if (needsPromotion) { |
| VariableDeclaration variable = new VariableDeclaration.forValue( |
| _createFieldGet(_field!), |
| type: _type!.withDeclaredNullability(Nullability.nullable), |
| )..fileOffset = _fragment.nameOffset; |
| return new Let( |
| variable, |
| new VariableGet(variable, _type)..fileOffset = _fragment.nameOffset, |
| ); |
| } else { |
| return _createFieldGet(_field!); |
| } |
| } |
| |
| /// Creates an [Expression] that reads [field]. |
| Expression _createFieldGet(Field field) { |
| if (field.isStatic) { |
| return new StaticGet(field)..fileOffset = _fragment.nameOffset; |
| } else { |
| // No substitution needed for the result type, since any type parameters |
| // in there are also in scope at the access site. |
| return new InstanceGet( |
| InstanceAccessKind.Instance, |
| new ThisExpression()..fileOffset = _fragment.nameOffset, |
| field.name, |
| interfaceTarget: field, |
| resultType: field.type, |
| )..fileOffset = _fragment.nameOffset; |
| } |
| } |
| |
| /// Creates an [Expression] that writes [value] to [field]. |
| Expression _createFieldSet(Field field, Expression value) { |
| if (field.isStatic) { |
| return new StaticSet(field, value)..fileOffset = _fragment.nameOffset; |
| } else { |
| return new InstanceSet( |
| InstanceAccessKind.Instance, |
| new ThisExpression()..fileOffset = _fragment.nameOffset, |
| field.name, |
| value, |
| interfaceTarget: field, |
| )..fileOffset = _fragment.nameOffset; |
| } |
| } |
| |
| Statement _createGetterBody( |
| CoreTypes coreTypes, |
| String name, |
| Expression? initializer, |
| ); |
| |
| Procedure? _createSetter( |
| Uri fileUri, |
| int charOffset, |
| Reference? reference, { |
| required bool isCovariantByDeclaration, |
| }) { |
| VariableDeclaration parameter = |
| new VariableDeclaration("${_fragment.name}#param") |
| ..isCovariantByDeclaration = isCovariantByDeclaration |
| ..fileOffset = _fragment.nameOffset; |
| return new Procedure( |
| dummyName, |
| ProcedureKind.Setter, |
| new FunctionNode( |
| null, |
| positionalParameters: [parameter], |
| returnType: const VoidType(), |
| ) |
| ..fileOffset = charOffset |
| ..fileEndOffset = _fragment.endOffset, |
| fileUri: fileUri, |
| reference: reference, |
| ) |
| ..fileOffset = charOffset |
| ..fileEndOffset = _fragment.endOffset; |
| } |
| |
| Statement _createSetterBody( |
| CoreTypes coreTypes, |
| String name, |
| VariableDeclaration parameter, |
| ); |
| |
| @override |
| DartType get type { |
| assert( |
| _type != null, |
| "Type has not been computed for field ${_fragment.name}.", |
| ); |
| return _type!; |
| } |
| |
| /// Updates the field/getter/setter types of [_field], [_lateGetter] and |
| /// [_lateSetter] to match the value of [_type]. |
| /// |
| /// This allows for creating the members and computing the type in arbitrary |
| /// order. |
| void _updateMemberTypes() { |
| DartType? type = _type; |
| Field? field = _field; |
| if (type != null && type is! InferredType && field != null) { |
| field.type = type.withDeclaredNullability(Nullability.nullable); |
| _lateGetter!.function.returnType = type; |
| _lateSetter?.function.positionalParameters.single.type = type; |
| if (!type.isPotentiallyNullable && !_forceIncludeIsSetField) { |
| // We only need the is-set field if the field is potentially nullable. |
| // Otherwise we use `null` to signal that the field is uninitialized. |
| _lateIsSetField = null; |
| } |
| } |
| } |
| |
| @override |
| void set type(DartType value) { |
| assert( |
| _type == null || _type is InferredType, |
| "Type has already been computed for field ${_fragment.name}.", |
| ); |
| _type = value; |
| _updateMemberTypes(); |
| } |
| |
| @override |
| void setCovariantByClass() { |
| if (_field!.hasSetter) { |
| _field!.isCovariantByClass = true; |
| } |
| _lateSetter?.function.positionalParameters.single.isCovariantByClass = true; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Field get field => _field!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Member get builtMember => _field!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Annotatable> get annotatables { |
| List<Annotatable> list = [_lateGetter!]; |
| if (_lateSetter != null) { |
| list.add(_lateSetter!); |
| } |
| return list; |
| } |
| |
| @override |
| Member get readTarget => _lateGetter!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference get readTargetReference => _lateGetter!.reference; |
| |
| @override |
| Member? get writeTarget => _lateSetter; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference? get writeTargetReference => _lateSetter?.reference; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Reference> get exportedReferenceMembers { |
| if (_lateSetter != null) { |
| return [_lateGetter!.reference, _lateSetter!.reference]; |
| } |
| return [_lateGetter!.reference]; |
| } |
| |
| @override |
| void buildOutlineNode( |
| SourceLibraryBuilder libraryBuilder, |
| NameScheme nameScheme, |
| PropertyReferences references, { |
| required bool isAbstractOrExternal, |
| required List<TypeParameter>? classTypeParameters, |
| }) { |
| _field = |
| new Field.mutable( |
| dummyName, |
| fileUri: _fragment.fileUri, |
| fieldReference: references.fieldReference, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset |
| ..isInternalImplementation = true; |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.Field, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_field!); |
| switch (_isSetStrategy) { |
| case late_lowering.IsSetStrategy.useSentinelOrNull: |
| case late_lowering.IsSetStrategy.forceUseSentinel: |
| // [_lateIsSetField] is never needed. |
| break; |
| case late_lowering.IsSetStrategy.forceUseIsSetField: |
| case late_lowering.IsSetStrategy.useIsSetFieldOrNull: |
| _lateIsSetField = |
| new Field.mutable(dummyName, fileUri: _fragment.fileUri) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset |
| ..isInternalImplementation = true; |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.IsSetField, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_lateIsSetField!); |
| break; |
| } |
| _lateGetter = |
| new Procedure( |
| dummyName, |
| ProcedureKind.Getter, |
| new FunctionNode(null) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset, |
| fileUri: _fragment.fileUri, |
| reference: references.getterReference, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset; |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.Getter, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_lateGetter!); |
| _lateSetter = _createSetter( |
| _fragment.fileUri, |
| _fragment.nameOffset, |
| references.setterReference, |
| isCovariantByDeclaration: _fragment.modifiers.isCovariant, |
| ); |
| if (_lateSetter != null) { |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.Setter, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_lateSetter!); |
| } |
| |
| bool isInstanceMember = |
| !_fragment.builder.isStatic && !_fragment.builder.isTopLevel; |
| bool isExtensionMember = _fragment.builder.isExtensionMember; |
| bool isExtensionTypeMember = _fragment.builder.isExtensionTypeMember; |
| if (isExtensionMember) { |
| _field! |
| ..isStatic = true |
| ..isExtensionMember = isExtensionMember; |
| isInstanceMember = false; |
| } else if (isExtensionTypeMember) { |
| _field! |
| ..isStatic = _fragment.builder.isStatic |
| ..isExtensionTypeMember = true; |
| } else { |
| _field! |
| ..isStatic = !isInstanceMember |
| ..isExtensionMember = false; |
| } |
| if (_lateIsSetField != null) { |
| _lateIsSetField! |
| ..isStatic = !isInstanceMember |
| ..isExtensionMember = isExtensionMember |
| ..isExtensionTypeMember = isExtensionTypeMember |
| ..type = libraryBuilder.loader.createCoreType( |
| 'bool', |
| Nullability.nonNullable, |
| ); |
| } |
| _lateGetter! |
| ..isStatic = !isInstanceMember |
| ..isExtensionMember = isExtensionMember |
| ..isExtensionTypeMember = isExtensionTypeMember; |
| if (_lateSetter != null) { |
| _lateSetter! |
| ..isStatic = !isInstanceMember |
| ..isExtensionMember = isExtensionMember |
| ..isExtensionTypeMember = isExtensionTypeMember; |
| } |
| _updateMemberTypes(); |
| } |
| |
| @override |
| void registerMembers(BuildNodesCallback f) { |
| f(member: _field!, kind: BuiltMemberKind.LateBackingField); |
| if (_lateIsSetField != null) { |
| _forceIncludeIsSetField = true; |
| f(member: _lateIsSetField!, kind: BuiltMemberKind.LateIsSetField); |
| } |
| f(member: _lateGetter!, kind: BuiltMemberKind.LateGetter); |
| if (_lateSetter != null) { |
| f(member: _lateSetter!, kind: BuiltMemberKind.LateSetter); |
| } |
| } |
| |
| @override |
| List<ClassMember> get localMembers => [ |
| new _SynthesizedFieldClassMember( |
| _fragment.builder, |
| _lateGetter!, |
| _fragment.builder.memberName, |
| _SynthesizedFieldMemberKind.LateGetterSetter, |
| ClassMemberKind.Getter, |
| _fragment.uriOffset, |
| ), |
| ]; |
| |
| @override |
| List<ClassMember> get localSetters => _lateSetter != null |
| ? [ |
| new _SynthesizedFieldClassMember( |
| _fragment.builder, |
| _lateSetter!, |
| _fragment.builder.memberName, |
| _SynthesizedFieldMemberKind.LateGetterSetter, |
| ClassMemberKind.Setter, |
| _fragment.uriOffset, |
| ), |
| ] |
| : const []; |
| |
| @override |
| void registerSuperCall() { |
| _field!.transformerFlags |= TransformerFlag.superCalls; |
| } |
| } |
| |
| mixin NonFinalLate on AbstractLateFieldEncoding { |
| @override |
| Statement _createSetterBody( |
| CoreTypes coreTypes, |
| String name, |
| VariableDeclaration parameter, |
| ) { |
| assert(_type != null, "Type has not been computed for field $name."); |
| return late_lowering.createSetterBody( |
| coreTypes, |
| _fragment.nameOffset, |
| name, |
| parameter, |
| _type!, |
| shouldReturnValue: false, |
| createVariableWrite: (Expression value) => |
| _createFieldSet(_field!, value), |
| createIsSetWrite: (Expression value) => |
| _createFieldSet(_lateIsSetField!, value), |
| isSetEncoding: isSetEncoding, |
| ); |
| } |
| } |
| |
| mixin LateWithoutInitializer on AbstractLateFieldEncoding { |
| @override |
| Statement _createGetterBody( |
| CoreTypes coreTypes, |
| String name, |
| Expression? initializer, |
| ) { |
| assert(_type != null, "Type has not been computed for field $name."); |
| return late_lowering.createGetterBodyWithoutInitializer( |
| coreTypes, |
| _fragment.nameOffset, |
| name, |
| type, |
| createVariableRead: _createFieldRead, |
| createIsSetRead: () => _createFieldGet(_lateIsSetField!), |
| isSetEncoding: isSetEncoding, |
| forField: true, |
| ); |
| } |
| |
| @override |
| void buildImplicitDefaultValue() { |
| throw new UnsupportedError("$runtimeType.buildImplicitDefaultValue"); |
| } |
| |
| @override |
| Initializer buildImplicitInitializer() { |
| throw new UnsupportedError("$runtimeType.buildImplicitInitializer"); |
| } |
| |
| @override |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }) { |
| throw new UnsupportedError("$runtimeType.buildDuplicatedInitializer"); |
| } |
| } |
| |
| class LateFieldWithoutInitializerEncoding extends AbstractLateFieldEncoding |
| with NonFinalLate, LateWithoutInitializer { |
| LateFieldWithoutInitializerEncoding( |
| super._fragment, { |
| required super.isSetStrategy, |
| }); |
| } |
| |
| class LateFieldWithInitializerEncoding extends AbstractLateFieldEncoding |
| with NonFinalLate { |
| LateFieldWithInitializerEncoding( |
| super._fragment, { |
| required super.isSetStrategy, |
| }); |
| |
| @override |
| Statement _createGetterBody( |
| CoreTypes coreTypes, |
| String name, |
| Expression? initializer, |
| ) { |
| assert(_type != null, "Type has not been computed for field $name."); |
| return late_lowering.createGetterWithInitializer( |
| coreTypes, |
| _fragment.nameOffset, |
| name, |
| _type!, |
| initializer!, |
| createVariableRead: _createFieldRead, |
| createVariableWrite: (Expression value) => |
| _createFieldSet(_field!, value), |
| createIsSetRead: () => _createFieldGet(_lateIsSetField!), |
| createIsSetWrite: (Expression value) => |
| _createFieldSet(_lateIsSetField!, value), |
| isSetEncoding: isSetEncoding, |
| ); |
| } |
| |
| @override |
| void buildImplicitDefaultValue() { |
| throw new UnsupportedError("$runtimeType.buildImplicitDefaultValue"); |
| } |
| |
| @override |
| Initializer buildImplicitInitializer() { |
| throw new UnsupportedError("$runtimeType.buildImplicitInitializer"); |
| } |
| |
| @override |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }) { |
| throw new UnsupportedError("$runtimeType.buildDuplicatedInitializer"); |
| } |
| } |
| |
| class LateFinalFieldWithoutInitializerEncoding extends AbstractLateFieldEncoding |
| with LateWithoutInitializer { |
| LateFinalFieldWithoutInitializerEncoding( |
| super._fragment, { |
| required super.isSetStrategy, |
| }); |
| |
| @override |
| Statement _createSetterBody( |
| CoreTypes coreTypes, |
| String name, |
| VariableDeclaration parameter, |
| ) { |
| assert(_type != null, "Type has not been computed for field $name."); |
| return late_lowering.createSetterBodyFinal( |
| coreTypes, |
| _fragment.nameOffset, |
| name, |
| parameter, |
| type, |
| shouldReturnValue: false, |
| createVariableRead: () => _createFieldGet(_field!), |
| createVariableWrite: (Expression value) => |
| _createFieldSet(_field!, value), |
| createIsSetRead: () => _createFieldGet(_lateIsSetField!), |
| createIsSetWrite: (Expression value) => |
| _createFieldSet(_lateIsSetField!, value), |
| isSetEncoding: isSetEncoding, |
| forField: true, |
| ); |
| } |
| } |
| |
| class LateFinalFieldWithInitializerEncoding extends AbstractLateFieldEncoding { |
| LateFinalFieldWithInitializerEncoding( |
| super._fragment, { |
| required super.isSetStrategy, |
| }); |
| |
| @override |
| Statement _createGetterBody( |
| CoreTypes coreTypes, |
| String name, |
| Expression? initializer, |
| ) { |
| assert(_type != null, "Type has not been computed for field $name."); |
| return late_lowering.createGetterWithInitializerWithRecheck( |
| coreTypes, |
| _fragment.nameOffset, |
| name, |
| _type!, |
| initializer!, |
| createVariableRead: _createFieldRead, |
| createVariableWrite: (Expression value) => |
| _createFieldSet(_field!, value), |
| createIsSetRead: () => _createFieldGet(_lateIsSetField!), |
| createIsSetWrite: (Expression value) => |
| _createFieldSet(_lateIsSetField!, value), |
| isSetEncoding: isSetEncoding, |
| forField: true, |
| ); |
| } |
| |
| @override |
| Procedure? _createSetter( |
| Uri fileUri, |
| int charOffset, |
| Reference? reference, { |
| required bool isCovariantByDeclaration, |
| }) => null; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Statement _createSetterBody( |
| CoreTypes coreTypes, |
| String name, |
| VariableDeclaration parameter, |
| ) => throw new UnsupportedError( |
| '$runtimeType._createSetterBody is not supported.', |
| ); |
| |
| @override |
| void buildImplicitDefaultValue() { |
| throw new UnsupportedError("$runtimeType.buildImplicitDefaultValue"); |
| } |
| |
| @override |
| Initializer buildImplicitInitializer() { |
| throw new UnsupportedError("$runtimeType.buildImplicitInitializer"); |
| } |
| |
| @override |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }) { |
| throw new UnsupportedError("$runtimeType.buildDuplicatedInitializer"); |
| } |
| } |
| |
| class AbstractOrExternalFieldEncoding implements FieldEncoding { |
| final FieldFragment _fragment; |
| final bool isAbstract; |
| final bool isExternal; |
| final bool _isExtensionInstanceMember; |
| final bool _isExtensionTypeInstanceMember; |
| |
| Procedure? _getter; |
| Procedure? _setter; |
| DartType? _type; |
| |
| AbstractOrExternalFieldEncoding( |
| this._fragment, { |
| required bool isExtensionInstanceMember, |
| required bool isExtensionTypeInstanceMember, |
| required this.isAbstract, |
| required this.isExternal, |
| bool isForcedExtension = false, |
| }) : _isExtensionInstanceMember = |
| (isExternal || isForcedExtension) && isExtensionInstanceMember, |
| _isExtensionTypeInstanceMember = |
| (isExternal || isForcedExtension) && isExtensionTypeInstanceMember; |
| |
| @override |
| DartType get type { |
| assert( |
| _type != null, |
| "Type has not been computed for field ${_fragment.name}.", |
| ); |
| return _type!; |
| } |
| |
| /// Updates the getter/setter types of [_getter] and [_setter] to match the |
| /// value of [_type]. |
| /// |
| /// This allows for creating the members and computing the type in arbitrary |
| /// order. |
| void _updateMemberTypes() { |
| Procedure? getter = _getter; |
| Procedure? setter = _setter; |
| DartType? type = _type; |
| if (type != null && type is! InferredType && getter != null) { |
| if (_isExtensionInstanceMember || _isExtensionTypeInstanceMember) { |
| DartType thisParameterType; |
| List<TypeParameter> typeParameters; |
| if (_isExtensionInstanceMember) { |
| SourceExtensionBuilder extensionBuilder = |
| _fragment.builder.parent as SourceExtensionBuilder; |
| thisParameterType = extensionBuilder.extension.onType; |
| typeParameters = extensionBuilder.extension.typeParameters; |
| } else { |
| SourceExtensionTypeDeclarationBuilder |
| extensionTypeDeclarationBuilder = |
| _fragment.builder.parent as SourceExtensionTypeDeclarationBuilder; |
| thisParameterType = extensionTypeDeclarationBuilder |
| .extensionTypeDeclaration |
| .declaredRepresentationType; |
| typeParameters = extensionTypeDeclarationBuilder |
| .extensionTypeDeclaration |
| .typeParameters; |
| } |
| if (typeParameters.isNotEmpty) { |
| FreshTypeParameters getterTypeParameters = getFreshTypeParameters( |
| typeParameters, |
| ); |
| getter.function.positionalParameters.first.type = getterTypeParameters |
| .substitute(thisParameterType); |
| getter.function.returnType = getterTypeParameters.substitute(type); |
| getter.function.typeParameters = |
| getterTypeParameters.freshTypeParameters; |
| setParents(getterTypeParameters.freshTypeParameters, getter.function); |
| |
| if (setter != null) { |
| FreshTypeParameters setterTypeParameters = getFreshTypeParameters( |
| typeParameters, |
| ); |
| setter.function.positionalParameters.first.type = |
| setterTypeParameters.substitute(thisParameterType); |
| setter.function.positionalParameters[1].type = setterTypeParameters |
| .substitute(type); |
| setter.function.typeParameters = |
| setterTypeParameters.freshTypeParameters; |
| setParents( |
| setterTypeParameters.freshTypeParameters, |
| setter.function, |
| ); |
| } |
| } else { |
| getter.function.returnType = type; |
| setter?.function.positionalParameters[1].type = type; |
| getter.function.positionalParameters.first.type = thisParameterType; |
| setter?.function.positionalParameters.first.type = thisParameterType; |
| } |
| } else { |
| getter.function.returnType = type; |
| if (setter != null) { |
| if (setter.kind == ProcedureKind.Method) { |
| // Coverage-ignore-block(suite): Not run. |
| setter.function.positionalParameters[1].type = type; |
| } else { |
| setter.function.positionalParameters.first.type = type; |
| } |
| } |
| } |
| } |
| } |
| |
| @override |
| void set type(DartType value) { |
| assert( |
| _type == null || _type is InferredType, |
| "Type has already been computed for field ${_fragment.name}.", |
| ); |
| _type = value; |
| _updateMemberTypes(); |
| } |
| |
| @override |
| void createBodies(CoreTypes coreTypes, Expression? initializer) { |
| // TODO(johnniwinther): Enable this assert. |
| //assert(initializer != null); |
| } |
| |
| @override |
| List<Initializer> createInitializer( |
| int fileOffset, |
| Expression value, { |
| required bool isSynthetic, |
| }) { |
| throw new UnsupportedError('ExternalFieldEncoding.createInitializer'); |
| } |
| |
| @override |
| void buildOutlineNode( |
| SourceLibraryBuilder libraryBuilder, |
| NameScheme nameScheme, |
| PropertyReferences references, { |
| required bool isAbstractOrExternal, |
| required List<TypeParameter>? classTypeParameters, |
| }) { |
| if (_isExtensionInstanceMember || _isExtensionTypeInstanceMember) { |
| _getter = |
| new Procedure( |
| dummyName, |
| ProcedureKind.Method, |
| new FunctionNode( |
| null, |
| positionalParameters: [ |
| new VariableDeclaration(syntheticThisName) |
| ..fileOffset = _fragment.nameOffset |
| ..isLowered = true, |
| ], |
| ), |
| fileUri: _fragment.fileUri, |
| reference: references.getterReference, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset; |
| nameScheme |
| .getProcedureMemberName(ProcedureKind.Getter, _fragment.name) |
| .attachMember(_getter!); |
| if (_fragment.hasSetter) { |
| VariableDeclaration parameter = |
| new VariableDeclaration("#externalFieldValue", isSynthesized: true) |
| ..isCovariantByDeclaration = _fragment.modifiers.isCovariant |
| ..fileOffset = _fragment.nameOffset; |
| _setter = |
| new Procedure( |
| dummyName, |
| ProcedureKind.Method, |
| new FunctionNode( |
| null, |
| positionalParameters: [ |
| new VariableDeclaration(syntheticThisName) |
| ..fileOffset = _fragment.nameOffset |
| ..isLowered = true, |
| parameter, |
| ], |
| returnType: const VoidType(), |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset, |
| fileUri: _fragment.fileUri, |
| reference: references.setterReference, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset; |
| nameScheme |
| .getProcedureMemberName(ProcedureKind.Setter, _fragment.name) |
| .attachMember(_setter!); |
| } |
| } else { |
| _getter = |
| new Procedure( |
| dummyName, |
| ProcedureKind.Getter, |
| new FunctionNode(null), |
| fileUri: _fragment.fileUri, |
| reference: references.getterReference, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset; |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.Getter, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_getter!); |
| if (!_fragment.modifiers.isFinal) { |
| VariableDeclaration parameter = |
| new VariableDeclaration("#externalFieldValue", isSynthesized: true) |
| ..isCovariantByDeclaration = _fragment.modifiers.isCovariant |
| ..fileOffset = _fragment.nameOffset; |
| _setter = |
| new Procedure( |
| dummyName, |
| ProcedureKind.Setter, |
| new FunctionNode( |
| null, |
| positionalParameters: [parameter], |
| returnType: const VoidType(), |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset, |
| fileUri: _fragment.fileUri, |
| reference: references.setterReference, |
| ) |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.endOffset; |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.Setter, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_setter!); |
| } |
| } |
| |
| bool isExtensionMember = _fragment.builder.isExtensionMember; |
| bool isExtensionTypeMember = _fragment.builder.isExtensionTypeMember; |
| bool isInstanceMember = |
| !isExtensionMember && |
| !isExtensionTypeMember && |
| !_fragment.builder.isStatic && |
| !_fragment.builder.isTopLevel; |
| _getter! |
| ..isConst = _fragment.modifiers.isConst |
| ..isStatic = !isInstanceMember |
| ..isExtensionMember = isExtensionMember |
| ..isExtensionTypeMember = isExtensionTypeMember |
| ..isAbstract = isAbstract && !isExternal |
| ..isExternal = isExternal; |
| |
| _setter |
| ?..isStatic = !isInstanceMember |
| ..isExtensionMember = isExtensionMember |
| ..isExtensionTypeMember = isExtensionTypeMember |
| ..isAbstract = isAbstract && !isExternal |
| ..isExternal = isExternal; |
| |
| _updateMemberTypes(); |
| } |
| |
| @override |
| void registerMembers(BuildNodesCallback f) { |
| BuiltMemberKind getterMemberKind; |
| if (_fragment.builder.isExtensionMember) { |
| getterMemberKind = BuiltMemberKind.ExtensionGetter; |
| } else if (_fragment.builder.isExtensionTypeMember) { |
| getterMemberKind = BuiltMemberKind.ExtensionTypeGetter; |
| } else { |
| getterMemberKind = BuiltMemberKind.Method; |
| } |
| f(member: _getter!, kind: getterMemberKind); |
| if (_setter != null) { |
| BuiltMemberKind setterMemberKind; |
| if (_fragment.builder.isExtensionMember) { |
| setterMemberKind = BuiltMemberKind.ExtensionSetter; |
| } else if (_fragment.builder.isExtensionTypeMember) { |
| setterMemberKind = BuiltMemberKind.ExtensionTypeSetter; |
| } else { |
| setterMemberKind = BuiltMemberKind.Method; |
| } |
| f(member: _setter!, kind: setterMemberKind); |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void setCovariantByClass() { |
| _setter!.function.positionalParameters.first.isCovariantByClass = true; |
| } |
| |
| @override |
| Field get field { |
| throw new UnsupportedError("ExternalFieldEncoding.field"); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Member get builtMember => _getter!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Annotatable> get annotatables { |
| List<Annotatable> list = [_getter!]; |
| if (_setter != null) { |
| list.add(_setter!); |
| } |
| return list; |
| } |
| |
| @override |
| Member get readTarget => _getter!; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference get readTargetReference => _getter!.reference; |
| |
| @override |
| Member? get writeTarget => _setter; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference? get writeTargetReference => _setter?.reference; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Reference> get exportedReferenceMembers { |
| if (_setter != null) { |
| return [_getter!.reference, _setter!.reference]; |
| } |
| return [_getter!.reference]; |
| } |
| |
| @override |
| List<ClassMember> get localMembers => <ClassMember>[ |
| new _SynthesizedFieldClassMember( |
| _fragment.builder, |
| _getter!, |
| _fragment.builder.memberName, |
| _SynthesizedFieldMemberKind.AbstractExternalGetterSetter, |
| ClassMemberKind.Getter, |
| _fragment.uriOffset, |
| ), |
| ]; |
| |
| @override |
| List<ClassMember> get localSetters => _setter != null |
| ? <ClassMember>[ |
| new _SynthesizedFieldClassMember( |
| _fragment.builder, |
| _setter!, |
| _fragment.builder.memberName, |
| _SynthesizedFieldMemberKind.AbstractExternalGetterSetter, |
| ClassMemberKind.Setter, |
| _fragment.uriOffset, |
| ), |
| ] |
| : const <ClassMember>[]; |
| |
| @override |
| void buildImplicitDefaultValue() { |
| throw new UnsupportedError("$runtimeType.buildImplicitDefaultValue"); |
| } |
| |
| @override |
| Initializer buildImplicitInitializer() { |
| throw new UnsupportedError("$runtimeType.buildImplicitInitializer"); |
| } |
| |
| @override |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }) { |
| return new ShadowInvalidFieldInitializer(type, value, effect) |
| ..fileOffset = fileOffset; |
| } |
| |
| @override |
| void registerSuperCall() { |
| throw new UnsupportedError( |
| "Unexpected call to ${runtimeType}.registerSuperCall().", |
| ); |
| } |
| } |
| |
| /// The encoding of an extension type declaration representation field. |
| class RepresentationFieldEncoding implements FieldEncoding { |
| final PrimaryConstructorFieldFragment _fragment; |
| |
| late Procedure _getter; |
| DartType? _type; |
| |
| RepresentationFieldEncoding(this._fragment); |
| @override |
| DartType get type { |
| assert( |
| _type != null, |
| "Type has not been computed for field ${_fragment.name}.", |
| ); |
| return _type!; |
| } |
| |
| @override |
| void set type(DartType value) { |
| assert( |
| _type == null || |
| // Coverage-ignore(suite): Not run. |
| _type is InferredType, |
| "Type has already been computed for field ${_fragment.name}.", |
| ); |
| _type = value; |
| if (value is! InferredType) { |
| _getter.function.returnType = value; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void createBodies(CoreTypes coreTypes, Expression? initializer) { |
| // TODO(johnniwinther): Enable this assert. |
| //assert(initializer != null); |
| } |
| |
| @override |
| List<Initializer> createInitializer( |
| int fileOffset, |
| Expression value, { |
| required bool isSynthetic, |
| }) { |
| return <Initializer>[ |
| new ExtensionTypeRepresentationFieldInitializer(_getter, value) |
| ..fileOffset = fileOffset, |
| ]; |
| } |
| |
| @override |
| void buildOutlineNode( |
| SourceLibraryBuilder libraryBuilder, |
| NameScheme nameScheme, |
| PropertyReferences references, { |
| required bool isAbstractOrExternal, |
| required List<TypeParameter>? classTypeParameters, |
| }) { |
| _getter = |
| new Procedure( |
| dummyName, |
| ProcedureKind.Getter, |
| new FunctionNode(null), |
| fileUri: _fragment.fileUri, |
| reference: references.getterReference, |
| ) |
| ..stubKind = ProcedureStubKind.RepresentationField |
| ..fileOffset = _fragment.nameOffset |
| ..fileEndOffset = _fragment.nameOffset; |
| nameScheme |
| .getFieldMemberName( |
| FieldNameType.RepresentationField, |
| _fragment.name, |
| isSynthesized: true, |
| ) |
| .attachMember(_getter); |
| _getter..isConst = false; |
| _getter |
| ..isStatic = false |
| ..isExtensionMember = false |
| ..isExtensionTypeMember = true |
| ..isAbstract = true |
| ..isExternal = false; |
| } |
| |
| @override |
| void registerMembers(BuildNodesCallback f) { |
| f(member: _getter, kind: BuiltMemberKind.ExtensionTypeRepresentationField); |
| } |
| |
| @override |
| void setCovariantByClass() { |
| throw new UnsupportedError("$runtimeType.setGenericCovariantImpl"); |
| } |
| |
| @override |
| Field get field { |
| throw new UnsupportedError("$runtimeType.field"); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Member get builtMember => _getter; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Annotatable> get annotatables => [_getter]; |
| |
| @override |
| Member get readTarget => _getter; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference get readTargetReference => _getter.reference; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Member? get writeTarget => null; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Reference? get writeTargetReference => null; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| Iterable<Reference> get exportedReferenceMembers => [_getter.reference]; |
| |
| @override |
| List<ClassMember> get localMembers => [ |
| new _SynthesizedFieldClassMember( |
| _fragment.builder, |
| _getter, |
| _fragment.builder.memberName, |
| _SynthesizedFieldMemberKind.RepresentationField, |
| ClassMemberKind.Getter, |
| _fragment.uriOffset, |
| ), |
| ]; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| List<ClassMember> get localSetters => const []; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void buildImplicitDefaultValue() { |
| // Not needed. |
| } |
| |
| @override |
| Initializer buildImplicitInitializer() { |
| return new ExtensionTypeRepresentationFieldInitializer( |
| _getter, |
| new NullLiteral(), |
| ); |
| } |
| |
| @override |
| Initializer buildErroneousInitializer( |
| Expression effect, |
| Expression value, { |
| required int fileOffset, |
| }) { |
| return new ShadowInvalidFieldInitializer(type, value, effect) |
| ..fileOffset = fileOffset; |
| } |
| |
| @override |
| void registerSuperCall() { |
| throw new UnsupportedError( |
| "Unexpected call to ${runtimeType}.registerSuperCall().", |
| ); |
| } |
| } |