blob: 6fc6f4366848afcaa7a3092c839b2a5908b340d6 [file] [log] [blame]
// 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' hide MapEntry;
import 'package:kernel/core_types.dart';
import '../names.dart';
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 with an initializer or a late local with an
/// initializer.
///
/// Late final field needs to detect writes during initialization and therefore
/// uses [createGetterWithInitializerWithRecheck] instead. Late final locals
/// cannot have writes during initialization since they are not in scope in
/// their own initializer.
Statement createGetterWithInitializer(CoreTypes coreTypes, int fileOffset,
String name, DartType type, Expression initializer,
{Expression createVariableRead({bool needsPromotion}),
Expression createVariableWrite(Expression value),
Expression createIsSetRead(),
Expression createIsSetWrite(Expression value),
bool useIsSetField}) {
assert(useIsSetField != null);
if (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 variable 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;
} else {
// Generate:
//
// return let # = _#field in # == null ? _#field = <init> : #;
VariableDeclaration variable = new VariableDeclaration.forValue(
createVariableRead(needsPromotion: false)..fileOffset = fileOffset,
type: type.withDeclaredNullability(Nullability.nullable))
..fileOffset = fileOffset;
return new ReturnStatement(
new Let(
variable,
new ConditionalExpression(
new MethodInvocation(
new VariableGet(variable)..fileOffset = fileOffset,
equalsName,
new Arguments(<Expression>[
new NullLiteral()..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset,
createVariableWrite(initializer)..fileOffset = fileOffset,
new VariableGet(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 with an initializer.
///
/// A late final field needs to detect writes during initialization for
/// which a `LateInitializationError` should be thrown. Late final locals
/// cannot have writes during initialization since they are not in scope in
/// their own initializer.
Statement createGetterWithInitializerWithRecheck(
CoreTypes coreTypes,
int fileOffset,
String name,
DartType type,
String variableKindName,
Expression initializer,
{Expression createVariableRead({bool needsPromotion}),
Expression createVariableWrite(Expression value),
Expression createIsSetRead(),
Expression createIsSetWrite(Expression value)}) {
Expression exception = new Throw(new ConstructorInvocation(
coreTypes.lateInitializationErrorConstructor,
new Arguments(<Expression>[
new StringLiteral(
"$variableKindName '${name}' has been assigned during "
"initialization.")
..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
VariableDeclaration temp =
new VariableDeclaration.forValue(initializer, type: type)
..fileOffset = fileOffset;
if (type.isPotentiallyNullable) {
// 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>[
temp,
new IfStatement(
createIsSetRead()..fileOffset = fileOffset,
new ExpressionStatement(exception)..fileOffset = fileOffset,
null)
..fileOffset = fileOffset,
new ExpressionStatement(
createVariableWrite(
new VariableGet(temp)..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 variable 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;
} else {
// Generate:
//
// return let #1 = _#field in #1 == null
// ? let #2 = <init> in _#field == null ? _#field = #2 : throw '...'
// : #1;
VariableDeclaration variable = new VariableDeclaration.forValue(
createVariableRead(needsPromotion: false)..fileOffset = fileOffset,
type: type.withDeclaredNullability(Nullability.nullable))
..fileOffset = fileOffset;
return new ReturnStatement(
new Let(
variable,
new ConditionalExpression(
new MethodInvocation(
new VariableGet(variable)..fileOffset = fileOffset,
equalsName,
new Arguments(<Expression>[
new NullLiteral()..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset,
new Let(
temp,
new ConditionalExpression(
new MethodInvocation(
createVariableRead(needsPromotion: false)
..fileOffset = fileOffset,
equalsName,
new Arguments(<Expression>[
new NullLiteral()..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset,
createVariableWrite(
new VariableGet(temp)..fileOffset = fileOffset)
..fileOffset = fileOffset,
exception,
type)
..fileOffset = fileOffset),
new VariableGet(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, String variableKindName,
{Expression createVariableRead({bool needsPromotion}),
Expression createIsSetRead(),
bool useIsSetField}) {
assert(useIsSetField != null);
Expression exception = new Throw(new ConstructorInvocation(
coreTypes.lateInitializationErrorConstructor,
new Arguments(<Expression>[
new StringLiteral(
"$variableKindName '${name}' has not been initialized.")
..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
if (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;
} else {
// Generate:
//
// return let # = _#field in # == null ? throw '...' : #;
VariableDeclaration variable = new VariableDeclaration.forValue(
createVariableRead()..fileOffset = fileOffset,
type: type.withDeclaredNullability(Nullability.nullable))
..fileOffset = fileOffset;
return new ReturnStatement(
new Let(
variable,
new ConditionalExpression(
new MethodInvocation(
new VariableGet(variable)..fileOffset = fileOffset,
equalsName,
new Arguments(<Expression>[
new NullLiteral()..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset,
exception,
new VariableGet(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,
VariableDeclaration parameter, DartType type,
{bool shouldReturnValue,
Expression createVariableWrite(Expression value),
Expression createIsSetWrite(Expression value),
bool useIsSetField}) {
assert(useIsSetField != null);
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);
if (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;
} else {
// 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,
VariableDeclaration parameter,
DartType type,
String variableKindName,
{bool shouldReturnValue,
Expression createVariableRead(),
Expression createVariableWrite(Expression value),
Expression createIsSetRead(),
Expression createIsSetWrite(Expression value),
bool useIsSetField}) {
assert(useIsSetField != null);
Expression exception = new Throw(new ConstructorInvocation(
coreTypes.lateInitializationErrorConstructor,
new Arguments(<Expression>[
new StringLiteral(
"${variableKindName} '${name}' has already been initialized.")
..fileOffset = fileOffset
])
..fileOffset = fileOffset)
..fileOffset = fileOffset)
..fileOffset = fileOffset;
Statement createReturn(Expression value) {
if (shouldReturnValue) {
return new ReturnStatement(value)..fileOffset = fileOffset;
} else {
return new ExpressionStatement(value)..fileOffset = fileOffset;
}
}
if (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;
} else {
// Generate:
//
// if (_#field == null) {
// return _#field = parameter;
// } else {
// throw '...';
// }
return new IfStatement(
new MethodInvocation(
createVariableRead()..fileOffset = fileOffset,
equalsName,
new Arguments(
<Expression>[new NullLiteral()..fileOffset = fileOffset])
..fileOffset = fileOffset)
..fileOffset = fileOffset,
createReturn(createVariableWrite(
new VariableGet(parameter)..fileOffset = fileOffset)
..fileOffset = fileOffset),
new ExpressionStatement(exception)..fileOffset = fileOffset,
)..fileOffset = fileOffset;
}
}