| // Copyright (c) 2016, 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. | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/core_types.dart'; | |
| import '../source/source_library_builder.dart'; | |
| import 'external_ast_helper.dart' as extern; | |
| const String lateFieldPrefix = '_#'; | |
| const String lateIsSetSuffix = '#isSet'; | |
| const String lateLocalPrefix = '#'; | |
| const String lateLocalGetterSuffix = '#get'; | |
| const String lateLocalSetterSuffix = '#set'; | |
| /// Creates the body for the synthesized getter used to encode the lowering | |
| /// of a late non-final field or local with an initializer. | |
| /// | |
| /// Late final fields and locals need to detect writes during initialization and | |
| /// therefore uses [createGetterWithInitializerWithRecheck] instead. | |
| Statement createGetterWithInitializer( | |
| CoreTypes coreTypes, | |
| int fileOffset, | |
| String name, | |
| DartType type, | |
| Expression initializer, { | |
| required Expression createVariableRead({bool needsPromotion}), | |
| required Expression createVariableWrite(Expression value), | |
| required Expression createIsSetRead(), | |
| required Expression createIsSetWrite(Expression value), | |
| required IsSetEncoding isSetEncoding, | |
| }) { | |
| switch (isSetEncoding) { | |
| case IsSetEncoding.useIsSetField: | |
| // Generate: | |
| // | |
| // if (!_#isSet#field) { | |
| // _#field = <init>; | |
| // _#isSet#field = true | |
| // } | |
| // return _#field; | |
| return new Block(<Statement>[ | |
| new IfStatement( | |
| new Not(createIsSetRead()..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| new Block(<Statement>[ | |
| new ExpressionStatement( | |
| createVariableWrite(initializer)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| new ExpressionStatement( | |
| createIsSetWrite(new BoolLiteral(true)..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ]), | |
| null, | |
| )..fileOffset = fileOffset, | |
| new ReturnStatement( | |
| // If [type] is a type parameter with undetermined nullability we | |
| // need to create a read of the field that is promoted to the type | |
| // variable type. | |
| createVariableRead(needsPromotion: type.isPotentiallyNonNullable), | |
| )..fileOffset = fileOffset, | |
| ])..fileOffset = fileOffset; | |
| case IsSetEncoding.useSentinel: | |
| // Generate: | |
| // | |
| // return let # = _#field in isSentinel(#) ? _#field = <init> : #; | |
| CachedExpression fieldCache = extern.createCachedExpression( | |
| expression: createVariableRead(needsPromotion: false) | |
| ..fileOffset = fileOffset, | |
| type: type.withDeclaredNullability(Nullability.nullable), | |
| ); | |
| return new ReturnStatement( | |
| extern.createLet( | |
| cache: fieldCache, | |
| body: new ConditionalExpression( | |
| new StaticInvocation( | |
| coreTypes.isSentinelMethod, | |
| new Arguments( | |
| new ExpressionList( | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| createVariableWrite(initializer)..fileOffset = fileOffset, | |
| new VariableGet(fieldCache.variable, type)..fileOffset = fileOffset, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| )..fileOffset = fileOffset; | |
| case IsSetEncoding.useNull: | |
| // Generate: | |
| // | |
| // return let # = _#field in # == null ? _#field = <init> : #; | |
| CachedExpression fieldCache = extern.createCachedExpression( | |
| expression: createVariableRead(needsPromotion: false) | |
| ..fileOffset = fileOffset, | |
| type: type.withDeclaredNullability(Nullability.nullable), | |
| ); | |
| return new ReturnStatement( | |
| extern.createLet( | |
| cache: fieldCache, | |
| body: new ConditionalExpression( | |
| new EqualsNull( | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| createVariableWrite(initializer)..fileOffset = fileOffset, | |
| new VariableGet(fieldCache.variable, type)..fileOffset = fileOffset, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| )..fileOffset = fileOffset; | |
| } | |
| } | |
| /// Creates the body for the synthesized getter used to encode the lowering | |
| /// of a late final field or local with an initializer. | |
| Statement createGetterWithInitializerWithRecheck( | |
| CoreTypes coreTypes, | |
| int fileOffset, | |
| String name, | |
| DartType type, | |
| Expression initializer, { | |
| required Expression createVariableRead({bool needsPromotion}), | |
| required Expression createVariableWrite(Expression value), | |
| required Expression createIsSetRead(), | |
| required Expression createIsSetWrite(Expression value), | |
| required IsSetEncoding isSetEncoding, | |
| required bool forField, | |
| }) { | |
| Constructor constructor = forField | |
| ? coreTypes.lateInitializationFieldAssignedDuringInitializationConstructor | |
| : coreTypes | |
| .lateInitializationLocalAssignedDuringInitializationConstructor; | |
| Expression exception = | |
| new Throw( | |
| new ConstructorInvocation( | |
| constructor, | |
| new Arguments( | |
| new ExpressionList( | |
| new StringLiteral(name)..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ) | |
| ..fileOffset = fileOffset | |
| ..forErrorHandling = true; | |
| CachedExpression initializerCache = extern.createCachedExpression( | |
| expression: initializer, | |
| type: type, | |
| fileOffset: fileOffset, | |
| ); | |
| switch (isSetEncoding) { | |
| case IsSetEncoding.useIsSetField: | |
| // Generate: | |
| // | |
| // if (!_#isSet#field) { | |
| // var temp = <init>; | |
| // if (_#isSet#field) throw '...' | |
| // _#field = temp; | |
| // _#isSet#field = true | |
| // } | |
| // return _#field; | |
| return new Block(<Statement>[ | |
| new IfStatement( | |
| new Not(createIsSetRead()..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| new Block(<Statement>[ | |
| new VariableStatement( | |
| extern.createVariableDeclaration( | |
| initializerCache.variable, | |
| initializer: initializerCache.value, | |
| ), | |
| )..fileOffset = initializerCache.fileOffset, | |
| new IfStatement( | |
| createIsSetRead()..fileOffset = fileOffset, | |
| new ExpressionStatement(exception)..fileOffset = fileOffset, | |
| null, | |
| )..fileOffset = fileOffset, | |
| new ExpressionStatement( | |
| createVariableWrite( | |
| new VariableGet(initializerCache.variable) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| new ExpressionStatement( | |
| createIsSetWrite(new BoolLiteral(true)..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ]), | |
| null, | |
| )..fileOffset = fileOffset, | |
| new ReturnStatement( | |
| // If [type] is a type parameter with undetermined nullability we | |
| // need to create a read of the field that is promoted to the type | |
| // variable type. | |
| createVariableRead(needsPromotion: type.isPotentiallyNonNullable), | |
| )..fileOffset = fileOffset, | |
| ])..fileOffset = fileOffset; | |
| case IsSetEncoding.useSentinel: | |
| // Generate: | |
| // | |
| // return let #1 = _#field in isSentinel(#1) | |
| // ? let #2 = <init> in isSentinel(_#field) | |
| // ? _#field = #2 : throw '...' | |
| // : #1; | |
| CachedExpression fieldCache = extern.createCachedExpression( | |
| expression: createVariableRead(needsPromotion: false) | |
| ..fileOffset = fileOffset, | |
| type: type, | |
| ); | |
| return new ReturnStatement( | |
| extern.createLet( | |
| cache: fieldCache, | |
| body: new ConditionalExpression( | |
| new StaticInvocation( | |
| coreTypes.isSentinelMethod, | |
| new Arguments( | |
| new ExpressionList( | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| extern.createLet( | |
| cache: initializerCache, | |
| body: new ConditionalExpression( | |
| new StaticInvocation( | |
| coreTypes.isSentinelMethod, | |
| new Arguments( | |
| new ExpressionList( | |
| createVariableRead(needsPromotion: false) | |
| ..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| createVariableWrite( | |
| new VariableGet(initializerCache.variable) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| exception, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| )..fileOffset = fileOffset; | |
| case IsSetEncoding.useNull: | |
| // Generate: | |
| // | |
| // return let #1 = _#field in #1 == null | |
| // ? let #2 = <init> in _#field == null | |
| // ? _#field = #2 : throw '...' | |
| // : #1; | |
| CachedExpression fieldCache = extern.createCachedExpression( | |
| expression: createVariableRead(needsPromotion: false) | |
| ..fileOffset = fileOffset, | |
| type: type.withDeclaredNullability(Nullability.nullable), | |
| ); | |
| return new ReturnStatement( | |
| extern.createLet( | |
| cache: fieldCache, | |
| body: new ConditionalExpression( | |
| new EqualsNull( | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| extern.createLet( | |
| cache: initializerCache, | |
| body: new ConditionalExpression( | |
| new EqualsNull( | |
| createVariableRead(needsPromotion: false) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| createVariableWrite( | |
| new VariableGet(initializerCache.variable) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| exception, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| new VariableGet(fieldCache.variable, type)..fileOffset = fileOffset, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| )..fileOffset = fileOffset; | |
| } | |
| } | |
| /// Creates the body for the synthesized getter used to encode the lowering | |
| /// of a late field or local without an initializer. | |
| Statement createGetterBodyWithoutInitializer( | |
| CoreTypes coreTypes, | |
| int fileOffset, | |
| String name, | |
| DartType type, { | |
| required Expression createVariableRead({bool needsPromotion}), | |
| required Expression createIsSetRead(), | |
| required IsSetEncoding isSetEncoding, | |
| required bool forField, | |
| }) { | |
| Expression exception = | |
| new Throw( | |
| new ConstructorInvocation( | |
| forField | |
| ? coreTypes.lateInitializationFieldNotInitializedConstructor | |
| : coreTypes.lateInitializationLocalNotInitializedConstructor, | |
| new Arguments( | |
| new ExpressionList( | |
| new StringLiteral(name)..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ) | |
| ..fileOffset = fileOffset | |
| ..forErrorHandling = true; | |
| switch (isSetEncoding) { | |
| case IsSetEncoding.useIsSetField: | |
| // Generate: | |
| // | |
| // return _#isSet#field ? _#field : throw '...'; | |
| return new ReturnStatement( | |
| new ConditionalExpression( | |
| createIsSetRead()..fileOffset = fileOffset, | |
| createVariableRead(needsPromotion: type.isPotentiallyNonNullable) | |
| ..fileOffset = fileOffset, | |
| exception, | |
| type, | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset; | |
| case IsSetEncoding.useSentinel: | |
| // Generate: | |
| // | |
| // return let # = _#field in isSentinel(#) ? throw '...' : #; | |
| CachedExpression fieldCache = extern.createCachedExpression( | |
| expression: createVariableRead()..fileOffset = fileOffset, | |
| type: type.withDeclaredNullability(Nullability.nullable), | |
| ); | |
| return new ReturnStatement( | |
| extern.createLet( | |
| cache: fieldCache, | |
| body: new ConditionalExpression( | |
| new StaticInvocation( | |
| coreTypes.isSentinelMethod, | |
| new Arguments( | |
| new ExpressionList( | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| exception, | |
| new VariableGet(fieldCache.variable, type)..fileOffset = fileOffset, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| )..fileOffset = fileOffset; | |
| case IsSetEncoding.useNull: | |
| // Generate: | |
| // | |
| // return let # = _#field in # == null ? throw '...' : #; | |
| CachedExpression fieldCache = extern.createCachedExpression( | |
| expression: createVariableRead()..fileOffset = fileOffset, | |
| type: type.withDeclaredNullability(Nullability.nullable), | |
| ); | |
| return new ReturnStatement( | |
| extern.createLet( | |
| cache: fieldCache, | |
| body: new ConditionalExpression( | |
| new EqualsNull( | |
| new VariableGet(fieldCache.variable)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| exception, | |
| new VariableGet(fieldCache.variable, type)..fileOffset = fileOffset, | |
| type, | |
| )..fileOffset = fileOffset, | |
| fileOffset: fileOffset, | |
| ), | |
| )..fileOffset = fileOffset; | |
| } | |
| } | |
| /// Creates the body for the synthesized setter used to encode the lowering | |
| /// of a non-final late field or local. | |
| Statement createSetterBody( | |
| CoreTypes coreTypes, | |
| int fileOffset, | |
| String name, | |
| PositionalParameter parameter, | |
| DartType type, { | |
| required bool shouldReturnValue, | |
| required Expression createVariableWrite(Expression value), | |
| required Expression createIsSetWrite(Expression value), | |
| required IsSetEncoding isSetEncoding, | |
| }) { | |
| Statement createReturn(Expression value) { | |
| if (shouldReturnValue) { | |
| return new ReturnStatement(value)..fileOffset = fileOffset; | |
| } else { | |
| return new ExpressionStatement(value)..fileOffset = fileOffset; | |
| } | |
| } | |
| Statement assignment = createReturn( | |
| createVariableWrite(new VariableGet(parameter)..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| ); | |
| switch (isSetEncoding) { | |
| case IsSetEncoding.useIsSetField: | |
| // Generate: | |
| // | |
| // _#isSet#field = true; | |
| // return _#field = parameter | |
| // | |
| return new Block([ | |
| new ExpressionStatement( | |
| createIsSetWrite(new BoolLiteral(true)..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| assignment, | |
| ])..fileOffset = fileOffset; | |
| case IsSetEncoding.useSentinel: | |
| case IsSetEncoding.useNull: | |
| // Generate: | |
| // | |
| // return _#field = parameter | |
| // | |
| return assignment; | |
| } | |
| } | |
| /// Creates the body for the synthesized setter used to encode the lowering | |
| /// of a final late field or local. | |
| Statement createSetterBodyFinal( | |
| CoreTypes coreTypes, | |
| int fileOffset, | |
| String name, | |
| Variable parameter, | |
| DartType type, { | |
| required bool shouldReturnValue, | |
| required Expression createVariableRead(), | |
| required Expression createVariableWrite(Expression value), | |
| required Expression createIsSetRead(), | |
| required Expression createIsSetWrite(Expression value), | |
| required IsSetEncoding isSetEncoding, | |
| required bool forField, | |
| }) { | |
| Expression exception = | |
| new Throw( | |
| new ConstructorInvocation( | |
| forField | |
| ? coreTypes.lateInitializationFieldAlreadyInitializedConstructor | |
| : coreTypes | |
| .lateInitializationLocalAlreadyInitializedConstructor, | |
| new Arguments( | |
| new ExpressionList( | |
| new StringLiteral(name)..fileOffset = fileOffset, | |
| ), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ) | |
| ..fileOffset = fileOffset | |
| ..forErrorHandling = true; | |
| Statement createReturn(Expression value) { | |
| if (shouldReturnValue) { | |
| return new ReturnStatement(value)..fileOffset = fileOffset; | |
| } else { | |
| return new ExpressionStatement(value)..fileOffset = fileOffset; | |
| } | |
| } | |
| switch (isSetEncoding) { | |
| case IsSetEncoding.useIsSetField: | |
| // Generate: | |
| // | |
| // if (_#isSet#field) { | |
| // throw '...'; | |
| // } else | |
| // _#isSet#field = true; | |
| // return _#field = parameter | |
| // } | |
| return new IfStatement( | |
| createIsSetRead()..fileOffset = fileOffset, | |
| new ExpressionStatement(exception)..fileOffset = fileOffset, | |
| new Block([ | |
| new ExpressionStatement( | |
| createIsSetWrite(new BoolLiteral(true)..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| createReturn( | |
| createVariableWrite( | |
| new VariableGet(parameter)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ), | |
| ])..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset; | |
| case IsSetEncoding.useSentinel: | |
| // Generate: | |
| // | |
| // if (isSentinel(_#field)) { | |
| // return _#field = parameter; | |
| // } else { | |
| // throw '...'; | |
| // } | |
| return new IfStatement( | |
| new StaticInvocation( | |
| coreTypes.isSentinelMethod, | |
| new Arguments( | |
| new ExpressionList(createVariableRead()..fileOffset = fileOffset), | |
| )..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| createReturn( | |
| createVariableWrite( | |
| new VariableGet(parameter)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ), | |
| new ExpressionStatement(exception)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset; | |
| case IsSetEncoding.useNull: | |
| // Generate: | |
| // | |
| // if (_#field == null) { | |
| // return _#field = parameter; | |
| // } else { | |
| // throw '...'; | |
| // } | |
| return new IfStatement( | |
| new EqualsNull(createVariableRead()..fileOffset = fileOffset) | |
| ..fileOffset = fileOffset, | |
| createReturn( | |
| createVariableWrite( | |
| new VariableGet(parameter)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset, | |
| ), | |
| new ExpressionStatement(exception)..fileOffset = fileOffset, | |
| )..fileOffset = fileOffset; | |
| } | |
| } | |
| /// Strategies for encoding whether a late field/local has been initialized. | |
| enum IsSetEncoding { | |
| /// Use a boolean `isSet` field/local. | |
| useIsSetField, | |
| /// Use `null` as sentinel value to signal an uninitialized field/locals. | |
| useNull, | |
| /// Use `createSentinel`and `isSentinel` from `dart:_internal` to generate | |
| /// and check a sentinel value to signal an uninitialized field/local. | |
| useSentinel, | |
| } | |
| /// Strategies for encoding of late fields and locals. | |
| enum IsSetStrategy { | |
| /// Always is use an `isSet` field/local to track whether the field/local has | |
| /// been initialized. | |
| forceUseIsSetField, | |
| /// Always use `createSentinel`and `isSentinel` from `dart:_internal` to | |
| /// generate and check a sentinel value to signal an uninitialized | |
| /// field/local. | |
| forceUseSentinel, | |
| /// For potentially nullable fields/locals use an `isSet` field/local to track | |
| /// whether the field/local has been initialized. Otherwise use `null` as | |
| /// sentinel value to signal an uninitialized field/local. | |
| /// | |
| /// This strategy can only be used with sound null safety mode. In weak mode | |
| /// non-nullable can be assigned `null` from legacy code and therefore `null` | |
| /// doesn't work as a sentinel. | |
| useIsSetFieldOrNull, | |
| /// For potentially nullable fields/locals use `createSentinel`and | |
| /// `isSentinel` from `dart:_internal` to generate and check a sentinel value | |
| /// to signal an uninitialized field/local. Otherwise use `null` as | |
| /// sentinel value to signal an uninitialized field/local. | |
| useSentinelOrNull, | |
| } | |
| IsSetStrategy computeIsSetStrategy(SourceLibraryBuilder libraryBuilder) { | |
| IsSetStrategy isSetStrategy = IsSetStrategy.useIsSetFieldOrNull; | |
| if (libraryBuilder.loader.target.backendTarget.supportsLateLoweringSentinel) { | |
| isSetStrategy = IsSetStrategy.useSentinelOrNull; | |
| } | |
| return isSetStrategy; | |
| } | |
| IsSetEncoding computeIsSetEncoding(DartType type, IsSetStrategy isSetStrategy) { | |
| switch (isSetStrategy) { | |
| case IsSetStrategy.forceUseIsSetField: | |
| return IsSetEncoding.useIsSetField; | |
| case IsSetStrategy.forceUseSentinel: | |
| return IsSetEncoding.useSentinel; | |
| case IsSetStrategy.useIsSetFieldOrNull: | |
| return type.isPotentiallyNullable | |
| ? IsSetEncoding.useIsSetField | |
| : IsSetEncoding.useNull; | |
| case IsSetStrategy.useSentinelOrNull: | |
| return type.isPotentiallyNullable | |
| ? IsSetEncoding.useSentinel | |
| : IsSetEncoding.useNull; | |
| } | |
| } | |
| /// Returns the name used for the variable that holds the value of a late | |
| /// lowered local by the given [name]. | |
| String computeLateLocalName(String name) { | |
| return '${lateLocalPrefix}$name'; | |
| } | |
| /// Returns the name used for the 'isSet' variable of a late lowered local by | |
| /// the given [name]. | |
| String computeLateLocalIsSetName(String name) { | |
| return '${lateLocalPrefix}${name}${lateIsSetSuffix}'; | |
| } | |
| /// Returns the name used for the getter function of a late lowered local by | |
| /// the given [name]. | |
| String computeLateLocalGetterName(String name) { | |
| return '${lateLocalPrefix}${name}${lateLocalGetterSuffix}'; | |
| } | |
| /// Returns the name used for the setter function of a late lowered local by | |
| /// the given [name]. | |
| String computeLateLocalSetterName(String name) { | |
| return '${lateLocalPrefix}${name}${lateLocalSetterSuffix}'; | |
| } |