-
Notifications
You must be signed in to change notification settings - Fork 36
/
ExpressionTransformer.java
executable file
·1697 lines (1471 loc) · 78.2 KB
/
ExpressionTransformer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.compiler.java.codegen;
import java.util.Arrays;
import java.util.LinkedList;
import com.redhat.ceylon.compiler.java.codegen.Operators.AssignmentOperatorTranslation;
import com.redhat.ceylon.compiler.java.codegen.Operators.OperatorTranslation;
import com.redhat.ceylon.compiler.java.codegen.Operators.OptimisationStrategy;
import com.redhat.ceylon.compiler.java.util.Decl;
import com.redhat.ceylon.compiler.java.util.Util;
import com.redhat.ceylon.compiler.loader.model.LazyMethod;
import com.redhat.ceylon.compiler.loader.model.LazyValue;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Functional;
import com.redhat.ceylon.compiler.typechecker.model.FunctionalParameter;
import com.redhat.ceylon.compiler.typechecker.model.Getter;
import com.redhat.ceylon.compiler.typechecker.model.Interface;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.Value;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.DefaultArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.FunctionArgument;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.SpecifierExpression;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Term;
import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCConditional;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCForLoop;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
/**
* This transformer deals with expressions only
*/
public class ExpressionTransformer extends AbstractTransformer {
static{
// only there to make sure this class is initialised before the enums defined in it, otherwise we
// get an initialisation error
Operators.init();
}
private boolean inStatement = false;
private boolean withinInvocation = false;
private boolean withinCallableInvocation = false;
public static ExpressionTransformer getInstance(Context context) {
ExpressionTransformer trans = context.get(ExpressionTransformer.class);
if (trans == null) {
trans = new ExpressionTransformer(context);
context.put(ExpressionTransformer.class, trans);
}
return trans;
}
private ExpressionTransformer(Context context) {
super(context);
}
//
// Statement expressions
public JCStatement transform(Tree.ExpressionStatement tree) {
// ExpressionStatements do not return any value, therefore we don't care about the type of the expressions.
inStatement = true;
JCStatement result = at(tree).Exec(transformExpression(tree.getExpression(), BoxingStrategy.INDIFFERENT, null));
inStatement = false;
return result;
}
public JCStatement transform(Tree.SpecifierStatement op) {
// SpecifierStatement do not return any value, therefore we don't care about the type of the expressions.
inStatement = true;
JCStatement result = at(op).Exec(transformAssignment(op, op.getBaseMemberExpression(), op.getSpecifierExpression().getExpression()));
inStatement = false;
return result;
}
//
// Any sort of expression
JCExpression transformExpression(final Tree.Term expr) {
return transformExpression(expr, BoxingStrategy.BOXED, null);
}
JCExpression transformExpression(final Tree.Term expr, BoxingStrategy boxingStrategy, ProducedType expectedType) {
if (expr == null) {
return null;
}
at(expr);
if (inStatement && boxingStrategy != BoxingStrategy.INDIFFERENT) {
// We're not directly inside the ExpressionStatement anymore
inStatement = false;
}
// Cope with things like ((expr))
// FIXME: shouldn't that be in the visitor?
Tree.Term term = expr;
while (term instanceof Tree.Expression) {
term = ((Tree.Expression)term).getTerm();
}
CeylonVisitor v = gen().visitor;
final ListBuffer<JCTree> prevDefs = v.defs;
final boolean prevInInitializer = v.inInitializer;
final ClassDefinitionBuilder prevClassBuilder = v.classBuilder;
JCExpression result = makeErroneous();
try {
v.defs = new ListBuffer<JCTree>();
v.inInitializer = false;
v.classBuilder = gen().current();
term.visit(v);
if (v.hasResult()) {
result = v.getSingleResult();
}
} finally {
v.classBuilder = prevClassBuilder;
v.inInitializer = prevInInitializer;
v.defs = prevDefs;
}
result = applyErasureAndBoxing(result, expr, boxingStrategy, expectedType);
return result;
}
JCExpression transform(FunctionArgument farg) {
Method model = farg.getDeclarationModel();
ProducedType callableType = typeFact().getCallableType(model.getType());
// TODO MPL
CallableBuilder callableBuilder = CallableBuilder.anonymous(
gen(),
farg.getExpression(),
model.getParameterLists().get(0),
callableType);
return callableBuilder.build();
}
//
// Boxing and erasure of expressions
private JCExpression applyErasureAndBoxing(JCExpression result, Tree.Term expr, BoxingStrategy boxingStrategy, ProducedType expectedType) {
ProducedType exprType = expr.getTypeModel();
boolean exprBoxed = !Util.isUnBoxed(expr);
return applyErasureAndBoxing(result, exprType, exprBoxed, boxingStrategy, expectedType);
}
JCExpression applyErasureAndBoxing(JCExpression result, ProducedType exprType,
boolean exprBoxed,
BoxingStrategy boxingStrategy, ProducedType expectedType) {
boolean canCast = false;
if (expectedType != null
// don't add cast to an erased type
&& !willEraseToObject(expectedType)
// don't add cast for null
&& !isNothing(exprType)) {
if(willEraseToObject(exprType)){
// Erased types need a type cast
JCExpression targetType = makeJavaType(expectedType, AbstractTransformer.TYPE_ARGUMENT);
exprType = expectedType;
result = make().TypeCast(targetType, result);
}else
canCast = true;
}
// we must do the boxing after the cast to the proper type
JCExpression ret = boxUnboxIfNecessary(result, exprBoxed, exprType, boxingStrategy);
// now check if we need variance casts
if (canCast) {
ret = applyVarianceCasts(ret, exprType, exprBoxed, boxingStrategy, expectedType);
}
ret = applySelfTypeCasts(ret, exprType, exprBoxed, boxingStrategy, expectedType);
ret = applyJavaTypeConversions(ret, exprType, expectedType, boxingStrategy);
return ret;
}
private JCExpression applyVarianceCasts(JCExpression result, ProducedType exprType,
boolean exprBoxed,
BoxingStrategy boxingStrategy, ProducedType expectedType) {
// unboxed types certainly don't need casting for variance
if(exprBoxed || boxingStrategy == BoxingStrategy.BOXED){
VarianceCastResult varianceCastResult = getVarianceCastResult(expectedType, exprType);
if(varianceCastResult != null){
// Types with variance types need a type cast, let's start with a raw cast to get rid
// of Java's type system constraint (javac doesn't grok multiple implementations of the same
// interface with different type params, which the JVM allows)
JCExpression targetType = makeJavaType(expectedType, AbstractTransformer.WANT_RAW_TYPE);
// do not change exprType here since this is just a Java workaround
result = make().TypeCast(targetType, result);
// now, because a raw cast is losing a lot of info, can we do better?
if(varianceCastResult.isBetterCastAvailable()){
// let's recast that to something finer than a raw cast
targetType = makeJavaType(varianceCastResult.castType, AbstractTransformer.TYPE_ARGUMENT);
result = make().TypeCast(targetType, result);
}
}
}
return result;
}
private JCExpression applySelfTypeCasts(JCExpression result, ProducedType exprType,
boolean exprBoxed,
BoxingStrategy boxingStrategy, ProducedType expectedType) {
final ProducedType selfType = exprType.getDeclaration().getSelfType();
if (selfType != null) {
JCExpression targetType = makeJavaType(selfType, AbstractTransformer.TYPE_ARGUMENT);
result = make().TypeCast(targetType, result);
}
return result;
}
private JCExpression applyJavaTypeConversions(JCExpression ret, ProducedType exprType, ProducedType expectedType, BoxingStrategy boxingStrategy) {
ProducedType definiteExprType = simplifyType(exprType);
String convertFrom = definiteExprType.getUnderlyingType();
ProducedType definiteExpectedType = null;
String convertTo = null;
if(expectedType != null){
definiteExpectedType = simplifyType(expectedType);
convertTo = definiteExpectedType.getUnderlyingType();
}
// check for identity conversion
if(convertFrom != null && convertFrom.equals(convertTo))
return ret;
boolean arrayUnbox = boxingStrategy == BoxingStrategy.UNBOXED && definiteExpectedType != null && isCeylonArray(definiteExpectedType);
if(convertTo != null){
if(convertTo.equals("byte"))
ret = make().TypeCast(syms().byteType, ret);
else if(convertTo.equals("short"))
ret = make().TypeCast(syms().shortType, ret);
else if(convertTo.equals("int"))
ret = make().TypeCast(syms().intType, ret);
else if(convertTo.equals("float"))
ret = make().TypeCast(syms().floatType, ret);
else if(convertTo.equals("char"))
ret = make().TypeCast(syms().charType, ret);
else if(convertTo.equals("byte[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.BYTE)), ret);
else if(convertTo.equals("short[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.SHORT)), ret);
else if(convertTo.equals("int[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.INT)), ret);
else if(convertTo.equals("long[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.LONG)), ret);
else if(convertTo.equals("float[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.FLOAT)), ret);
else if(convertTo.equals("double[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.DOUBLE)), ret);
else if(convertTo.equals("char[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.CHAR)), ret);
else if(convertTo.equals("boolean[]"))
ret = make().TypeCast(make().TypeArray(make().TypeIdent(TypeTags.BOOLEAN)), ret);
else if (arrayUnbox) {
String ct = convertTo.substring(0, convertTo.length() - 2);
ret = make().TypeCast(make().TypeArray(makeQuotedQualIdentFromString(ct)), ret);
}
} else if (arrayUnbox) {
ProducedType ct = getArrayComponentType(definiteExpectedType);
ret = make().TypeCast(make().TypeArray(makeJavaType(ct)), ret);
}
return ret;
}
private static class VarianceCastResult {
ProducedType castType;
VarianceCastResult(ProducedType castType){
this.castType = castType;
}
private VarianceCastResult(){}
boolean isBetterCastAvailable(){
return castType != null;
}
}
private static final VarianceCastResult RawCastVarianceResult = new VarianceCastResult();
private VarianceCastResult getVarianceCastResult(ProducedType expectedType, ProducedType exprType) {
// exactly the same type, doesn't need casting
if(exprType.isExactly(expectedType))
return null;
// if we're not trying to put it into an interface, there's no need
if(!(expectedType.getDeclaration() instanceof Interface))
return null;
// the interface must have type arguments, otherwise we can't use raw types
if(expectedType.getTypeArguments().isEmpty())
return null;
// see if any of those type arguments has variance
boolean hasVariance = false;
for(TypeParameter t : expectedType.getTypeArguments().keySet()){
if(t.isContravariant() || t.isCovariant()){
hasVariance = true;
break;
}
}
if(!hasVariance)
return null;
// see if we're inheriting the interface twice with different type parameters
java.util.List<ProducedType> satisfiedTypes = new LinkedList<ProducedType>();
for(ProducedType superType : exprType.getSupertypes()){
if(superType.getDeclaration() == expectedType.getDeclaration())
satisfiedTypes.add(superType);
}
// we need at least two instantiations
if(satisfiedTypes.size() <= 1)
return null;
boolean needsCast = false;
// we need at least one that differs
for(ProducedType superType : satisfiedTypes){
if(!exprType.isExactly(superType)){
needsCast = true;
break;
}
}
// no cast needed if they are all the same type
if(!needsCast)
return null;
// find the better cast match
for(ProducedType superType : satisfiedTypes){
if(expectedType.isExactly(superType))
return new VarianceCastResult(superType);
}
// nothing better than a raw cast (Stef: not sure that can happen)
return RawCastVarianceResult;
}
//
// Literals
JCExpression ceylonLiteral(String s) {
JCLiteral lit = make().Literal(s);
return lit;
}
public JCExpression transform(Tree.StringLiteral string) {
String value = string
.getText()
.substring(1, string.getText().length() - 1);
at(string);
return ceylonLiteral(value);
}
public JCExpression transform(Tree.QuotedLiteral string) {
String value = string
.getText()
.substring(1, string.getText().length() - 1);
JCExpression result = makeSelect(makeIdent(syms().ceylonQuotedType), "instance");
return at(string).Apply(null, result, List.<JCTree.JCExpression>of(make().Literal(value)));
}
public JCExpression transform(Tree.CharLiteral lit) {
JCExpression expr = make().Literal(TypeTags.CHAR, (int) lit.getText().charAt(1));
// XXX make().Literal(lit.value) doesn't work here... something
// broken in javac?
return expr;
}
public JCExpression transform(Tree.FloatLiteral lit) {
double value = Double.parseDouble(lit.getText());
// Don't need to handle the negative infinity and negative zero cases
// because Ceylon Float literals have no sign
if (value == Double.POSITIVE_INFINITY) {
return makeErroneous(lit, "Literal so large it is indistinguishable from infinity");
} else if (value == 0.0 && !lit.getText().equals("0.0")) {
return makeErroneous(lit, "Literal so small it is indistinguishable from zero");
}
JCExpression expr = make().Literal(value);
return expr;
}
private JCExpression integerLiteral(Node node, String num) {
try {
return make().Literal(Long.parseLong(num));
} catch (NumberFormatException e) {
return makeErroneous(node, "Literal outside representable range");
}
}
public JCExpression transform(Tree.NaturalLiteral lit) {
return integerLiteral(lit, lit.getText());
}
public JCExpression transformStringExpression(Tree.StringTemplate expr) {
at(expr);
JCExpression builder;
builder = make().NewClass(null, null, makeFQIdent("java","lang","StringBuilder"), List.<JCExpression>nil(), null);
java.util.List<Tree.StringLiteral> literals = expr.getStringLiterals();
java.util.List<Tree.Expression> expressions = expr.getExpressions();
for (int ii = 0; ii < literals.size(); ii += 1) {
Tree.StringLiteral literal = literals.get(ii);
if (!"\"\"".equals(literal.getText())) {// ignore empty string literals
at(literal);
builder = make().Apply(null, makeSelect(builder, "append"), List.<JCExpression>of(transform(literal)));
}
if (ii == expressions.size()) {
// The loop condition includes the last literal, so break out
// after that because we've already exhausted all the expressions
break;
}
Tree.Expression expression = expressions.get(ii);
at(expression);
// Here in both cases we don't need a type cast for erasure
if (isCeylonBasicType(expression.getTypeModel())) {// TODO: Test should be erases to String, long, int, boolean, char, byte, float, double
// If erases to a Java primitive just call append, don't box it just to call format.
String method = isCeylonCharacter(expression.getTypeModel()) ? "appendCodePoint" : "append";
builder = make().Apply(null, makeSelect(builder, method), List.<JCExpression>of(transformExpression(expression, BoxingStrategy.UNBOXED, null)));
} else {
JCMethodInvocation formatted = make().Apply(null, makeSelect(transformExpression(expression), "toString"), List.<JCExpression>nil());
builder = make().Apply(null, makeSelect(builder, "append"), List.<JCExpression>of(formatted));
}
}
return make().Apply(null, makeSelect(builder, "toString"), List.<JCExpression>nil());
}
public JCExpression transform(Tree.SequenceEnumeration value) {
at(value);
if (value.getExpressionList() == null) {
return makeEmpty();
} else {
java.util.List<Tree.Expression> list = value.getExpressionList().getExpressions();
ProducedType seqElemType = value.getTypeModel().getTypeArgumentList().get(0);
return makeSequence(list, seqElemType);
}
}
public JCTree transform(Tree.This expr) {
at(expr);
if (needDollarThis(expr.getScope())) {
return makeUnquotedIdent("$this");
}
if (isWithinCallableInvocation()) {
return makeSelect(makeJavaType(expr.getTypeModel()), "this");
}
return makeUnquotedIdent("this");
}
public JCTree transform(Tree.Super expr) {
at(expr);
return makeUnquotedIdent("super");
}
public JCTree transform(Tree.Outer expr) {
at(expr);
ProducedType outerClass = com.redhat.ceylon.compiler.typechecker.model.Util.getOuterClassOrInterface(expr.getScope());
return makeSelect(makeQuotedIdent(outerClass.getDeclaration().getName()), "this");
}
//
// Unary and Binary operators that can be overridden
//
// Unary operators
public JCExpression transform(Tree.NotOp op) {
// No need for an erasure cast since Term must be Boolean and we never need to erase that
JCExpression term = transformExpression(op.getTerm(), Util.getBoxingStrategy(op), null);
JCUnary jcu = at(op).Unary(JCTree.NOT, term);
return jcu;
}
public JCExpression transform(Tree.IsOp op) {
// we don't need any erasure type cast for an "is" test
JCExpression expression = transformExpression(op.getTerm());
at(op);
String varName = tempName();
JCExpression test = makeTypeTest(null, varName, op.getType().getTypeModel());
return makeLetExpr(varName, List.<JCStatement>nil(), make().Type(syms().objectType), expression, test);
}
public JCTree transform(Tree.Nonempty op) {
// we don't need any erasure type cast for a "nonempty" test
JCExpression expression = transformExpression(op.getTerm());
at(op);
String varName = tempName();
JCExpression test = makeNonEmptyTest(null, varName);
return makeLetExpr(varName, List.<JCStatement>nil(), make().Type(syms().objectType), expression, test);
}
public JCTree transform(Tree.Exists op) {
// we don't need any erasure type cast for an "exists" test
JCExpression expression = transformExpression(op.getTerm());
at(op);
return make().Binary(JCTree.NE, expression, makeNull());
}
public JCExpression transform(Tree.PositiveOp op) {
return transformOverridableUnaryOperator(op, op.getUnit().getInvertableDeclaration());
}
public JCExpression transform(Tree.NegativeOp op) {
if (op.getTerm() instanceof Tree.NaturalLiteral) {
// To cope with -9223372036854775808 we can't just parse the
// number separately from the sign
return integerLiteral(op.getTerm(), "-" + op.getTerm().getText());
}
return transformOverridableUnaryOperator(op, op.getUnit().getInvertableDeclaration());
}
public JCExpression transform(Tree.UnaryOperatorExpression op) {
return transformOverridableUnaryOperator(op, (ProducedType)null);
}
private JCExpression transformOverridableUnaryOperator(Tree.UnaryOperatorExpression op, Interface compoundType) {
ProducedType leftType = getSupertype(op.getTerm(), compoundType);
return transformOverridableUnaryOperator(op, leftType);
}
private JCExpression transformOverridableUnaryOperator(Tree.UnaryOperatorExpression op, ProducedType expectedType) {
at(op);
Tree.Term term = op.getTerm();
OperatorTranslation operator = Operators.getOperator(op.getClass());
if (operator == null) {
return makeErroneous(op);
}
if(operator.getOptimisationStrategy(op, this).useJavaOperator()){
// optimisation for unboxed types
JCExpression expr = transformExpression(term, BoxingStrategy.UNBOXED, expectedType);
// unary + is essentially a NOOP
if(operator == OperatorTranslation.UNARY_POSITIVE)
return expr;
return make().Unary(operator.javacOperator, expr);
}
return make().Apply(null, makeSelect(transformExpression(term, BoxingStrategy.BOXED, expectedType),
Util.getGetterName(operator.ceylonMethod)), List.<JCExpression> nil());
}
//
// Binary operators
public JCExpression transform(Tree.NotEqualOp op) {
OperatorTranslation operator = Operators.OperatorTranslation.BINARY_EQUAL;
OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(op, this);
// we want it unboxed only if the operator is optimised
// we don't care about the left erased type, since equals() is on Object
JCExpression left = transformExpression(op.getLeftTerm(), optimisationStrategy.getBoxingStrategy(), null);
// we don't care about the right erased type, since equals() is on Object
JCExpression expr = transformOverridableBinaryOperator(op, operator, optimisationStrategy, left, null);
return at(op).Unary(JCTree.NOT, expr);
}
public JCExpression transform(Tree.RangeOp op) {
// we need to get the range bound type
ProducedType comparableType = getSupertype(op.getLeftTerm(), op.getUnit().getComparableDeclaration());
ProducedType paramType = getTypeArgument(comparableType);
JCExpression lower = transformExpression(op.getLeftTerm(), BoxingStrategy.BOXED, paramType);
JCExpression upper = transformExpression(op.getRightTerm(), BoxingStrategy.BOXED, paramType);
ProducedType rangeType = typeFact().getRangeType(op.getLeftTerm().getTypeModel());
JCExpression typeExpr = makeJavaType(rangeType, CeylonTransformer.CLASS_NEW);
return at(op).NewClass(null, null, typeExpr, List.<JCExpression> of(lower, upper), null);
}
public JCExpression transform(Tree.EntryOp op) {
// no erasure cast needed for both terms
JCExpression key = transformExpression(op.getLeftTerm());
JCExpression elem = transformExpression(op.getRightTerm());
ProducedType entryType = typeFact().getEntryType(op.getLeftTerm().getTypeModel(), op.getRightTerm().getTypeModel());
JCExpression typeExpr = makeJavaType(entryType, CeylonTransformer.CLASS_NEW);
return at(op).NewClass(null, null, typeExpr , List.<JCExpression> of(key, elem), null);
}
public JCTree transform(Tree.DefaultOp op) {
JCExpression left = transformExpression(op.getLeftTerm());
JCExpression right = transformExpression(op.getRightTerm());
String varName = tempName();
JCExpression varIdent = makeUnquotedIdent(varName);
JCExpression test = at(op).Binary(JCTree.NE, varIdent, makeNull());
JCExpression cond = make().Conditional(test , varIdent, right);
JCExpression typeExpr = makeJavaType(op.getTypeModel(), NO_PRIMITIVES);
return makeLetExpr(varName, null, typeExpr, left, cond);
}
public JCTree transform(Tree.ThenOp op) {
JCExpression left = transformExpression(op.getLeftTerm(), Util.getBoxingStrategy(op.getLeftTerm()), typeFact().getBooleanDeclaration().getType());
JCExpression right = transformExpression(op.getRightTerm());
return make().Conditional(left , right, makeNull());
}
public JCTree transform(Tree.InOp op) {
JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.BOXED, typeFact().getObjectDeclaration().getType());
JCExpression right = transformExpression(op.getRightTerm(), BoxingStrategy.BOXED, typeFact().getCategoryDeclaration().getType());
String varName = tempName();
JCExpression varIdent = makeUnquotedIdent(varName);
JCExpression contains = at(op).Apply(null, makeSelect(right, "contains"), List.<JCExpression> of(varIdent));
JCExpression typeExpr = makeJavaType(op.getLeftTerm().getTypeModel(), NO_PRIMITIVES);
return makeLetExpr(varName, null, typeExpr, left, contains);
}
// Logical operators
public JCExpression transform(Tree.LogicalOp op) {
OperatorTranslation operator = Operators.getOperator(op.getClass());
if(operator == null){
return makeErroneous(op, "Not supported yet: "+op.getNodeType());
}
// Both terms are Booleans and can't be erased to anything
JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.UNBOXED, null);
return transformLogicalOp(op, operator, left, op.getRightTerm());
}
private JCExpression transformLogicalOp(Node op, OperatorTranslation operator,
JCExpression left, Tree.Term rightTerm) {
// Both terms are Booleans and can't be erased to anything
JCExpression right = transformExpression(rightTerm, BoxingStrategy.UNBOXED, null);
return at(op).Binary(operator.javacOperator, left, right);
}
// Comparison operators
public JCExpression transform(Tree.IdenticalOp op){
// The only thing which might be unboxed is boolean, and we can follow the rules of == for optimising it,
// which are simple and require that both types be booleans to be unboxed, otherwise they must be boxed
OptimisationStrategy optimisationStrategy = OperatorTranslation.BINARY_EQUAL.getOptimisationStrategy(op, this);
JCExpression left = transformExpression(op.getLeftTerm(), optimisationStrategy.getBoxingStrategy(), null);
JCExpression right = transformExpression(op.getRightTerm(), optimisationStrategy.getBoxingStrategy(), null);
return at(op).Binary(JCTree.EQ, left, right);
}
public JCExpression transform(Tree.ComparisonOp op) {
return transformOverridableBinaryOperator(op, op.getUnit().getComparableDeclaration());
}
public JCExpression transform(Tree.CompareOp op) {
return transformOverridableBinaryOperator(op, op.getUnit().getComparableDeclaration());
}
// Arithmetic operators
public JCExpression transform(Tree.ArithmeticOp op) {
return transformOverridableBinaryOperator(op, op.getUnit().getNumericDeclaration());
}
public JCExpression transform(Tree.SumOp op) {
return transformOverridableBinaryOperator(op, op.getUnit().getSummableDeclaration());
}
public JCExpression transform(Tree.RemainderOp op) {
return transformOverridableBinaryOperator(op, op.getUnit().getIntegralDeclaration());
}
// Overridable binary operators
public JCExpression transform(Tree.BinaryOperatorExpression op) {
return transformOverridableBinaryOperator(op, null, null);
}
private JCExpression transformOverridableBinaryOperator(Tree.BinaryOperatorExpression op, Interface compoundType) {
ProducedType leftType = getSupertype(op.getLeftTerm(), compoundType);
ProducedType rightType = getTypeArgument(leftType);
return transformOverridableBinaryOperator(op, leftType, rightType);
}
private JCExpression transformOverridableBinaryOperator(Tree.BinaryOperatorExpression op, ProducedType leftType, ProducedType rightType) {
OperatorTranslation operator = Operators.getOperator(op.getClass());
if (operator == null) {
return makeErroneous(op);
}
OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(op, this);
JCExpression left = transformExpression(op.getLeftTerm(), optimisationStrategy.getBoxingStrategy(), leftType);
return transformOverridableBinaryOperator(op, operator, optimisationStrategy, left, rightType);
}
private JCExpression transformOverridableBinaryOperator(Tree.BinaryOperatorExpression op,
OperatorTranslation originalOperator, OptimisationStrategy optimisatonStrategy,
JCExpression left, ProducedType rightType) {
JCExpression result = null;
JCExpression right = transformExpression(op.getRightTerm(), optimisatonStrategy.getBoxingStrategy(), rightType);
// optimise if we can
if(optimisatonStrategy.useJavaOperator()){
return make().Binary(originalOperator.javacOperator, left, right);
}
boolean loseComparison =
originalOperator == OperatorTranslation.BINARY_SMALLER
|| originalOperator == OperatorTranslation.BINARY_SMALL_AS
|| originalOperator == OperatorTranslation.BINARY_LARGER
|| originalOperator == OperatorTranslation.BINARY_LARGE_AS;
// for comparisons we need to invoke compare()
OperatorTranslation actualOperator = originalOperator;
if (loseComparison) {
actualOperator = Operators.OperatorTranslation.BINARY_COMPARE;
if (actualOperator == null) {
return makeErroneous();
}
}
result = at(op).Apply(null, makeSelect(left, actualOperator.ceylonMethod), List.of(right));
if (loseComparison) {
result = at(op).Apply(null, makeSelect(result, originalOperator.ceylonMethod), List.<JCExpression> nil());
}
return result;
}
//
// Operator-Assignment expressions
public JCExpression transform(final Tree.ArithmeticAssignmentOp op){
final AssignmentOperatorTranslation operator = Operators.getAssignmentOperator(op.getClass());
if(operator == null){
return makeErroneous(op, "Not supported yet: "+op.getNodeType());
}
// see if we can optimise it
if(op.getUnboxed() && Util.isDirectAccessVariable(op.getLeftTerm())){
return optimiseAssignmentOperator(op, operator);
}
// we can use unboxed types if both operands are unboxed
final boolean boxResult = !op.getUnboxed();
// find the proper type
Interface compoundType = op.getUnit().getNumericDeclaration();
if(op instanceof Tree.AddAssignOp){
compoundType = op.getUnit().getSummableDeclaration();
}else if(op instanceof Tree.RemainderAssignOp){
compoundType = op.getUnit().getIntegralDeclaration();
}
final ProducedType leftType = getSupertype(op.getLeftTerm(), compoundType);
final ProducedType rightType = getMostPreciseType(op.getLeftTerm(), getTypeArgument(leftType, 0));
// we work on boxed types
return transformAssignAndReturnOperation(op, op.getLeftTerm(), boxResult,
leftType, rightType,
new AssignAndReturnOperationFactory(){
@Override
public JCExpression getNewValue(JCExpression previousValue) {
// make this call: previousValue OP RHS
JCExpression ret = transformOverridableBinaryOperator(op, operator.binaryOperator,
boxResult ? OptimisationStrategy.NONE : OptimisationStrategy.OPTIMISE,
previousValue, rightType);
ret = unAutoPromote(ret, rightType);
return ret;
}
});
}
public JCExpression transform(Tree.BitwiseAssignmentOp op){
return makeErroneous(op, "Not supported yet: "+op.getNodeType());
}
public JCExpression transform(final Tree.LogicalAssignmentOp op){
final AssignmentOperatorTranslation operator = Operators.getAssignmentOperator(op.getClass());
if(operator == null){
return makeErroneous(op, "Not supported yet: "+op.getNodeType());
}
// optimise if we can
if(Util.isDirectAccessVariable(op.getLeftTerm())){
return optimiseAssignmentOperator(op, operator);
}
ProducedType valueType = op.getLeftTerm().getTypeModel();
// we work on unboxed types
return transformAssignAndReturnOperation(op, op.getLeftTerm(), false,
valueType, valueType, new AssignAndReturnOperationFactory(){
@Override
public JCExpression getNewValue(JCExpression previousValue) {
// make this call: previousValue OP RHS
return transformLogicalOp(op, operator.binaryOperator,
previousValue, op.getRightTerm());
}
});
}
private JCExpression optimiseAssignmentOperator(final Tree.AssignmentOp op, final AssignmentOperatorTranslation operator) {
// we don't care about their types since they're unboxed and we know it
JCExpression left = transformExpression(op.getLeftTerm(), BoxingStrategy.UNBOXED, null);
JCExpression right = transformExpression(op.getRightTerm(), BoxingStrategy.UNBOXED, null);
return at(op).Assignop(operator.javacOperator, left, right);
}
// Postfix operator
public JCExpression transform(Tree.PostfixOperatorExpression expr) {
OperatorTranslation operator = Operators.getOperator(expr.getClass());
if(operator == null){
return makeErroneous(expr, "Not supported yet: "+expr.getNodeType());
}
OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(expr, this);
boolean canOptimise = optimisationStrategy.useJavaOperator();
// only fully optimise if we don't have to access the getter/setter
if(canOptimise && Util.isDirectAccessVariable(expr.getTerm())){
JCExpression term = transformExpression(expr.getTerm(), BoxingStrategy.UNBOXED, expr.getTypeModel());
return at(expr).Unary(operator.javacOperator, term);
}
Tree.Term term = expr.getTerm();
Interface compoundType = expr.getUnit().getOrdinalDeclaration();
ProducedType valueType = getSupertype(expr.getTerm(), compoundType);
ProducedType returnType = getMostPreciseType(term, getTypeArgument(valueType, 0));
List<JCVariableDecl> decls = List.nil();
List<JCStatement> stats = List.nil();
JCExpression result = null;
// we can optimise that case a bit sometimes
boolean boxResult = !canOptimise;
// attr++
// (let $tmp = attr; attr = $tmp.getSuccessor(); $tmp;)
if(term instanceof Tree.BaseMemberExpression){
JCExpression getter = transform((Tree.BaseMemberExpression)term, null);
at(expr);
// Type $tmp = attr
JCExpression exprType = makeJavaType(returnType, boxResult ? NO_PRIMITIVES : 0);
Name varName = names().fromString(tempName("op"));
// make sure we box the results if necessary
getter = applyErasureAndBoxing(getter, term, boxResult ? BoxingStrategy.BOXED : BoxingStrategy.UNBOXED, returnType);
JCVariableDecl tmpVar = make().VarDef(make().Modifiers(0), varName, exprType, getter);
decls = decls.prepend(tmpVar);
// attr = $tmp.getSuccessor()
JCExpression successor;
if(canOptimise){
// use +1/-1 if we can optimise a bit
successor = make().Binary(operator == OperatorTranslation.UNARY_POSTFIX_INCREMENT ? JCTree.PLUS : JCTree.MINUS,
make().Ident(varName), makeInteger(1));
successor = unAutoPromote(successor, returnType);
}else{
successor = make().Apply(null,
makeSelect(make().Ident(varName), operator.ceylonMethod),
List.<JCExpression>nil());
// make sure the result is boxed if necessary, the result of successor/predecessor is always boxed
successor = boxUnboxIfNecessary(successor, true, term.getTypeModel(), Util.getBoxingStrategy(term));
}
JCExpression assignment = transformAssignment(expr, term, successor);
stats = stats.prepend(at(expr).Exec(assignment));
// $tmp
result = make().Ident(varName);
}
else if(term instanceof Tree.QualifiedMemberExpression){
// e.attr++
// (let $tmpE = e, $tmpV = $tmpE.attr; $tmpE.attr = $tmpV.getSuccessor(); $tmpV;)
Tree.QualifiedMemberExpression qualified = (Tree.QualifiedMemberExpression) term;
// transform the primary, this will get us a boxed primary
JCExpression e = transformQualifiedMemberPrimary(qualified);
at(expr);
// Type $tmpE = e
JCExpression exprType = makeJavaType(qualified.getTarget().getQualifyingType(), NO_PRIMITIVES);
Name varEName = names().fromString(tempName("opE"));
JCVariableDecl tmpEVar = make().VarDef(make().Modifiers(0), varEName, exprType, e);
// Type $tmpV = $tmpE.attr
JCExpression attrType = makeJavaType(returnType, boxResult ? NO_PRIMITIVES : 0);
Name varVName = names().fromString(tempName("opV"));
JCExpression getter = transformMemberExpression(qualified, make().Ident(varEName), null);
// make sure we box the results if necessary
getter = applyErasureAndBoxing(getter, term, boxResult ? BoxingStrategy.BOXED : BoxingStrategy.UNBOXED, returnType);
JCVariableDecl tmpVVar = make().VarDef(make().Modifiers(0), varVName, attrType, getter);
// define all the variables
decls = decls.prepend(tmpVVar);
decls = decls.prepend(tmpEVar);
// $tmpE.attr = $tmpV.getSuccessor()
JCExpression successor;
if(canOptimise){
// use +1/-1 if we can optimise a bit
successor = make().Binary(operator == OperatorTranslation.UNARY_POSTFIX_INCREMENT ? JCTree.PLUS : JCTree.MINUS,
make().Ident(varVName), makeInteger(1));
successor = unAutoPromote(successor, returnType);
}else{
successor = make().Apply(null,
makeSelect(make().Ident(varVName), operator.ceylonMethod),
List.<JCExpression>nil());
// make sure the result is boxed if necessary, the result of successor/predecessor is always boxed
successor = boxUnboxIfNecessary(successor, true, term.getTypeModel(), Util.getBoxingStrategy(term));
}
JCExpression assignment = transformAssignment(expr, term, make().Ident(varEName), successor);
stats = stats.prepend(at(expr).Exec(assignment));
// $tmpV
result = make().Ident(varVName);
}else{
return makeErroneous(term, "Not supported yet");
}
// e?.attr++ is probably not legal
// a[i]++ is not for M1 but will be:
// (let $tmpA = a, $tmpI = i, $tmpV = $tmpA.item($tmpI); $tmpA.setItem($tmpI, $tmpV.getSuccessor()); $tmpV;)
// a?[i]++ is probably not legal
// a[i1..i1]++ and a[i1...]++ are probably not legal
// a[].attr++ and a[].e.attr++ are probably not legal
return make().LetExpr(decls, stats, result);
}
// Prefix operator
public JCExpression transform(final Tree.PrefixOperatorExpression expr) {
final OperatorTranslation operator = Operators.getOperator(expr.getClass());
if(operator == null){
return makeErroneous(expr, "Not supported yet: "+expr.getNodeType());
}
OptimisationStrategy optimisationStrategy = operator.getOptimisationStrategy(expr, this);
final boolean canOptimise = optimisationStrategy.useJavaOperator();
Term term = expr.getTerm();
// only fully optimise if we don't have to access the getter/setter
if(canOptimise && Util.isDirectAccessVariable(term)){
JCExpression jcTerm = transformExpression(term, BoxingStrategy.UNBOXED, expr.getTypeModel());
return at(expr).Unary(operator.javacOperator, jcTerm);
}
Interface compoundType = expr.getUnit().getOrdinalDeclaration();
ProducedType valueType = getSupertype(term, compoundType);
final ProducedType returnType = getMostPreciseType(term, getTypeArgument(valueType, 0));
// we work on boxed types unless we could have optimised
return transformAssignAndReturnOperation(expr, term, !canOptimise,
valueType, returnType, new AssignAndReturnOperationFactory(){
@Override
public JCExpression getNewValue(JCExpression previousValue) {
// use +1/-1 if we can optimise a bit
if(canOptimise){
JCExpression ret = make().Binary(operator == OperatorTranslation.UNARY_PREFIX_INCREMENT ? JCTree.PLUS : JCTree.MINUS,
previousValue, makeInteger(1));
ret = unAutoPromote(ret, returnType);
return ret;
}
// make this call: previousValue.getSuccessor() or previousValue.getPredecessor()
return make().Apply(null, makeSelect(previousValue, operator.ceylonMethod), List.<JCExpression>nil());
}
});
}
//
// Function to deal with expressions that have side-effects
private interface AssignAndReturnOperationFactory {
JCExpression getNewValue(JCExpression previousValue);
}
private JCExpression transformAssignAndReturnOperation(Node operator, Tree.Term term,
boolean boxResult, ProducedType valueType, ProducedType returnType,
AssignAndReturnOperationFactory factory){
List<JCVariableDecl> decls = List.nil();
List<JCStatement> stats = List.nil();