2
2
// for details. All rights reserved. Use of this source code is governed by a
3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
+ /// This library implements a kernel2kernel constant evaluation transformation.
6
+ ///
7
+ /// Even though it is expected that the frontend does not emit kernel AST which
8
+ /// contains compile-time errors, this transformation still performs some
9
+ /// valiation and throws a [ConstantEvaluationError] if there was a compile-time
10
+ /// errors.
11
+ ///
12
+ /// Due to the lack information which is is only available in the front-end,
13
+ /// this validation is incomplete (e.g. whether an integer literal used the
14
+ /// hexadecimal syntax or not).
15
+ ///
16
+ /// Furthermore due to the lowering of certain constructs in the front-end
17
+ /// (e.g. '??') we need to support a super-set of the normal constant expression
18
+ /// language. Issue(http://dartbug.com/31799)
5
19
library kernel.transformations.constants;
6
20
7
21
import '../kernel.dart' ;
8
22
import '../ast.dart' ;
23
+ import '../core_types.dart' ;
9
24
import '../type_algebra.dart' ;
25
+ import '../type_environment.dart' ;
26
+ import '../class_hierarchy.dart' ;
10
27
import 'treeshaker.dart' show findNativeName;
11
28
12
29
Program transformProgram (Program program, ConstantsBackend backend,
13
- {bool keepFields: false }) {
14
- transformLibraries (program.libraries, backend, keepFields: keepFields);
30
+ {bool keepFields: false ,
31
+ bool strongMode: false ,
32
+ bool enableAsserts: false ,
33
+ CoreTypes coreTypes,
34
+ ClassHierarchy hierarchy}) {
35
+ coreTypes ?? = new CoreTypes (program);
36
+ hierarchy ?? = new ClosedWorldClassHierarchy (program);
37
+
38
+ final typeEnvironment =
39
+ new TypeEnvironment (coreTypes, hierarchy, strongMode: strongMode);
40
+
41
+ transformLibraries (program.libraries, backend, coreTypes, typeEnvironment,
42
+ keepFields: keepFields,
43
+ strongMode: strongMode,
44
+ enableAsserts: enableAsserts);
15
45
return program;
16
46
}
17
47
18
48
void transformLibraries (List <Library > libraries, ConstantsBackend backend,
19
- {bool keepFields: false , bool keepVariables: false }) {
20
- final ConstantsTransformer constantsTransformer =
21
- new ConstantsTransformer (backend, keepFields, keepVariables);
49
+ CoreTypes coreTypes, TypeEnvironment typeEnvironment,
50
+ {bool keepFields: false ,
51
+ bool keepVariables: false ,
52
+ bool strongMode: false ,
53
+ bool enableAsserts: false }) {
54
+ final ConstantsTransformer constantsTransformer = new ConstantsTransformer (
55
+ backend,
56
+ keepFields,
57
+ keepVariables,
58
+ coreTypes,
59
+ typeEnvironment,
60
+ strongMode,
61
+ enableAsserts);
22
62
for (final Library library in libraries) {
23
63
for (final Field field in library.fields.toList ()) {
24
64
constantsTransformer.convertField (field);
@@ -39,19 +79,39 @@ void transformLibraries(List<Library> libraries, ConstantsBackend backend,
39
79
constantsTransformer.convertConstructor (constructor);
40
80
}
41
81
}
82
+ for (final Typedef td in library.typedefs) {
83
+ constantsTransformer.convertTypedef (td);
84
+ }
85
+
86
+ if (! keepFields) {
87
+ // The transformer API does not iterate over `Library.additionalExports`,
88
+ // so we manually delete the references to shaken nodes.
89
+ library.additionalExports.removeWhere ((Reference reference) {
90
+ return reference.canonicalName == null ;
91
+ });
92
+ }
42
93
}
43
94
}
44
95
45
96
class ConstantsTransformer extends Transformer {
46
97
final ConstantEvaluator constantEvaluator;
98
+ final CoreTypes coreTypes;
99
+ final TypeEnvironment typeEnvironment;
47
100
48
101
/// Whether to preserve constant [Field] s. All use-sites will be rewritten.
49
102
final bool keepFields;
50
103
final bool keepVariables;
51
104
52
105
ConstantsTransformer (
53
- ConstantsBackend backend, this .keepFields, this .keepVariables)
54
- : constantEvaluator = new ConstantEvaluator (backend);
106
+ ConstantsBackend backend,
107
+ this .keepFields,
108
+ this .keepVariables,
109
+ this .coreTypes,
110
+ this .typeEnvironment,
111
+ bool strongMode,
112
+ bool enableAsserts)
113
+ : constantEvaluator = new ConstantEvaluator (
114
+ backend, typeEnvironment, coreTypes, strongMode, enableAsserts);
55
115
56
116
// Transform the library/class members:
57
117
@@ -79,6 +139,13 @@ class ConstantsTransformer extends Transformer {
79
139
});
80
140
}
81
141
142
+ void convertTypedef (Typedef td) {
143
+ // A typedef can have annotations on variables which are constants.
144
+ constantEvaluator.withNewEnvironment (() {
145
+ td.accept (this );
146
+ });
147
+ }
148
+
82
149
// Handle definition of constants:
83
150
84
151
visitVariableDeclaration (VariableDeclaration node) {
@@ -166,6 +233,10 @@ class ConstantsTransformer extends Transformer {
166
233
167
234
class ConstantEvaluator extends RecursiveVisitor {
168
235
final ConstantsBackend backend;
236
+ final CoreTypes coreTypes;
237
+ final TypeEnvironment typeEnvironment;
238
+ final bool strongMode;
239
+ final bool enableAsserts;
169
240
170
241
final Map <Constant , Constant > canonicalizationCache;
171
242
final Map <Node , Constant > nodeCache;
@@ -177,7 +248,8 @@ class ConstantEvaluator extends RecursiveVisitor {
177
248
InstanceBuilder instanceBuilder;
178
249
EvaluationEnvironment env;
179
250
180
- ConstantEvaluator (this .backend)
251
+ ConstantEvaluator (this .backend, this .typeEnvironment, this .coreTypes,
252
+ this .strongMode, this .enableAsserts)
181
253
: canonicalizationCache = < Constant , Constant > {},
182
254
nodeCache = < Node , Constant > {};
183
255
@@ -204,6 +276,8 @@ class ConstantEvaluator extends RecursiveVisitor {
204
276
}
205
277
206
278
visitIntLiteral (IntLiteral node) {
279
+ // The frontend will ensure the integer literals are in signed 64-bit range
280
+ // in strong mode.
207
281
return canonicalize (new IntConstant (node.value));
208
282
}
209
283
@@ -335,6 +409,21 @@ class ConstantEvaluator extends RecursiveVisitor {
335
409
typeArguments,
336
410
evaluatePositionalArguments (init.arguments),
337
411
evaluateNamedArguments (init.arguments));
412
+ } else if (init is AssertInitializer ) {
413
+ if (enableAsserts) {
414
+ final Constant condition = init.statement.condition.accept (this );
415
+
416
+ if (condition is BoolConstant ) {
417
+ if (! condition.value) {
418
+ final Constant message = init.statement.message? .accept (this );
419
+ throw new ConstantEvaluationError (
420
+ 'Assert initializer condition failed with message: $message .' );
421
+ }
422
+ } else {
423
+ throw new ConstantEvaluationError (
424
+ 'Assert initializer did not evaluate to a boolean condition.' );
425
+ }
426
+ }
338
427
} else {
339
428
throw new ConstantEvaluationError (
340
429
'Cannot evaluate constant with [${init .runtimeType }].' );
@@ -353,15 +442,19 @@ class ConstantEvaluator extends RecursiveVisitor {
353
442
354
443
// Handle == and != first (it's common between all types).
355
444
if (arguments.length == 1 && node.name.name == '==' ) {
356
- ensurePrimitiveConstant (receiver);
445
+ // TODO(http://dartbug.com/31799): Re-enable these checks.
446
+ //ensurePrimitiveConstant(receiver);
357
447
final right = arguments[0 ];
358
- ensurePrimitiveConstant (right);
448
+ // TODO(http://dartbug.com/31799): Re-enable these checks.
449
+ //ensurePrimitiveConstant(right);
359
450
return receiver == right ? trueConstant : falseConstant;
360
451
}
361
452
if (arguments.length == 1 && node.name.name == '!=' ) {
362
- ensurePrimitiveConstant (receiver);
453
+ // TODO(http://dartbug.com/31799): Re-enable these checks.
454
+ //ensurePrimitiveConstant(receiver);
363
455
final right = arguments[0 ];
364
- ensurePrimitiveConstant (right);
456
+ // TODO(http://dartbug.com/31799): Re-enable these checks.
457
+ //ensurePrimitiveConstant(right);
365
458
return receiver != right ? trueConstant : falseConstant;
366
459
}
367
460
@@ -498,12 +591,14 @@ class ConstantEvaluator extends RecursiveVisitor {
498
591
}
499
592
500
593
visitConditionalExpression (ConditionalExpression node) {
501
- final BoolConstant constant = evaluate (node.condition);
594
+ final Constant constant = evaluate (node.condition);
502
595
if (constant == trueConstant) {
503
596
return evaluate (node.then);
504
- } else {
505
- assert (constant == falseConstant);
597
+ } else if (constant == falseConstant) {
506
598
return evaluate (node.otherwise);
599
+ } else {
600
+ throw new ConstantEvaluationError (
601
+ 'Cannot use $constant as condition in a conditional expression.' );
507
602
}
508
603
}
509
604
@@ -593,14 +688,19 @@ class ConstantEvaluator extends RecursiveVisitor {
593
688
} else if (target.name.name == 'identical' ) {
594
689
// Ensure the "identical()" function comes from dart:core.
595
690
final parent = target.parent;
596
- if (parent is Library && parent.importUri == 'dart:core' ) {
691
+ if (parent is Library && parent == coreTypes.coreLibrary ) {
597
692
final positionalArguments =
598
693
evaluatePositionalArguments (node.arguments);
599
- final left = positionalArguments[0 ];
600
- ensurePrimitiveConstant (left);
601
- final right = positionalArguments[1 ];
602
- ensurePrimitiveConstant (right);
603
- return left == right ? trueConstant : falseConstant;
694
+ final Constant left = positionalArguments[0 ];
695
+ // TODO(http://dartbug.com/31799): Re-enable these checks.
696
+ //ensurePrimitiveConstant(left);
697
+ final Constant right = positionalArguments[1 ];
698
+ // TODO(http://dartbug.com/31799): Re-enable these checks.
699
+ //ensurePrimitiveConstant(right);
700
+ // Since we canonicalize constants during the evaluation, we can use
701
+ // identical here.
702
+ assert (left == right);
703
+ return identical (left, right) ? trueConstant : falseConstant;
604
704
}
605
705
}
606
706
}
@@ -609,8 +709,63 @@ class ConstantEvaluator extends RecursiveVisitor {
609
709
'Calling "$target " during constant evaluation is disallowed.' );
610
710
}
611
711
712
+ visitAsExpression (AsExpression node) {
713
+ final Constant constant = node.operand.accept (this );
714
+ ensureIsSubtype (constant, evaluateDartType (node.type));
715
+ return constant;
716
+ }
717
+
718
+ visitNot (Not node) {
719
+ final Constant constant = node.operand.accept (this );
720
+ if (constant is BoolConstant ) {
721
+ return constant == trueConstant ? falseConstant : trueConstant;
722
+ }
723
+ throw new ConstantEvaluationError (
724
+ 'A not expression must have a boolean operand.' );
725
+ }
726
+
727
+ visitSymbolLiteral (SymbolLiteral node) {
728
+ final value = canonicalize (new StringConstant (node.value));
729
+ return canonicalize (backend.buildSymbolConstant (value));
730
+ }
731
+
612
732
// Helper methods:
613
733
734
+ void ensureIsSubtype (Constant constant, DartType type) {
735
+ DartType constantType;
736
+ if (constant is NullConstant ) {
737
+ constantType = new InterfaceType (coreTypes.nullClass);
738
+ } else if (constant is BoolConstant ) {
739
+ constantType = new InterfaceType (coreTypes.boolClass);
740
+ } else if (constant is IntConstant ) {
741
+ constantType = new InterfaceType (coreTypes.intClass);
742
+ } else if (constant is DoubleConstant ) {
743
+ constantType = new InterfaceType (coreTypes.doubleClass);
744
+ } else if (constant is StringConstant ) {
745
+ constantType = new InterfaceType (coreTypes.stringClass);
746
+ } else if (constant is MapConstant ) {
747
+ constantType = new InterfaceType (
748
+ coreTypes.mapClass, < DartType > [constant.keyType, constant.valueType]);
749
+ } else if (constant is ListConstant ) {
750
+ constantType = new InterfaceType (
751
+ coreTypes.stringClass, < DartType > [constant.typeArgument]);
752
+ } else if (constant is InstanceConstant ) {
753
+ constantType = new InterfaceType (constant.klass, constant.typeArguments);
754
+ } else if (constant is TearOffConstant ) {
755
+ constantType = constant.procedure.function.functionType;
756
+ } else if (constant is TypeLiteralConstant ) {
757
+ constantType = new InterfaceType (coreTypes.typeClass);
758
+ } else {
759
+ throw new ConstantEvaluationError (
760
+ 'No support for obtaining the type of $constant .' );
761
+ }
762
+
763
+ if (! typeEnvironment.isSubtypeOf (constantType, type)) {
764
+ throw new ConstantEvaluationError (
765
+ 'Constant $constant is not a subtype of ${type }.' );
766
+ }
767
+ }
768
+
614
769
List <DartType > evaluateTypeArguments (Arguments arguments) {
615
770
return evaluateDartTypes (arguments.types);
616
771
}
@@ -686,7 +841,7 @@ class ConstantEvaluator extends RecursiveVisitor {
686
841
result = a - b;
687
842
break ;
688
843
case '*' :
689
- result = a - b;
844
+ result = a * b;
690
845
break ;
691
846
case '/' :
692
847
result = a / b;
@@ -695,13 +850,13 @@ class ConstantEvaluator extends RecursiveVisitor {
695
850
result = a ~ / b;
696
851
break ;
697
852
case '%' :
698
- result = a ~ / b;
853
+ result = a % b;
699
854
break ;
700
855
}
701
856
702
857
if (result != null ) {
703
858
return canonicalize (result is int
704
- ? new IntConstant (result)
859
+ ? new IntConstant (_wrapAroundInteger ( result) )
705
860
: new DoubleConstant (result as double ));
706
861
}
707
862
@@ -719,6 +874,16 @@ class ConstantEvaluator extends RecursiveVisitor {
719
874
throw new ConstantEvaluationError (
720
875
'Binary operation "$op " on num is disallowed.' );
721
876
}
877
+
878
+ int _wrapAroundInteger (int value) {
879
+ if (strongMode) {
880
+ return value.toSigned (64 );
881
+ }
882
+ return value;
883
+ }
884
+
885
+ static const kMaxInt64 = (1 << 63 ) - 1 ;
886
+ static const kMinInt64 = - (1 << 63 );
722
887
}
723
888
724
889
/// Holds the necessary information for a constant object, namely
@@ -798,6 +963,7 @@ abstract class ConstantsBackend {
798
963
List <DartType > typeArguments,
799
964
List <Constant > positionalArguments,
800
965
Map <String , Constant > namedArguments);
966
+ Constant buildSymbolConstant (StringConstant value);
801
967
802
968
Constant lowerListConstant (ListConstant constant);
803
969
Constant lowerMapConstant (MapConstant constant);
0 commit comments