2007-09-23 Marek Safar <marek.safar@gmail.com>
[mono.git] / mcs / gmcs / linq.cs
1 //
2 // linq.cs: support for query expressions
3 //
4 // Authors: Marek Safar (marek.safar@gmail.com)
5 //
6 // Licensed under the terms of the GNU GPL
7 //
8 // (C) 2007 Novell, Inc
9 //
10
11 using System;
12 using System.Reflection;
13 using System.Collections;
14
15 namespace Mono.CSharp.Linq
16 {
17         // NOTES:
18         // Expression should be IExpression to save some memory and make a few things
19         // easier to read
20         //
21         //
22
23         public class QueryExpression : AQueryClause
24         {
25                 Block block;
26
27                 public QueryExpression (Block block, AQueryClause query)
28                         : base (null, query.Location)
29                 {
30                         this.block = block;
31                         this.next = query;
32                 }
33
34                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
35                 {
36                         Parameter p = CreateBlockParameter (block);
37                         return next.BuildQueryClause (ec, lSide, p, ti);
38                 }
39
40                 protected override void CloneTo (CloneContext clonectx, Expression target)
41                 {
42                         QueryExpression t = (QueryExpression) target;
43                         t.block = (Block)block.Clone (clonectx);
44                         base.CloneTo (clonectx, t);
45                 }
46
47                 public override Expression DoResolve (EmitContext ec)
48                 {
49                         Expression e = BuildQueryClause (ec, null, null, null);
50                         e = e.Resolve (ec);
51                         return e;
52                 }
53
54                 protected override string MethodName {
55                         get { throw new NotSupportedException (); }
56                 }
57         }
58
59         public abstract class AQueryClause : Expression
60         {
61                 class QueryExpressionAccess : MemberAccess
62                 {
63                         public QueryExpressionAccess (Expression expr, string methodName, Location loc)
64                                 : base (expr, methodName, loc)
65                         {
66                         }
67
68                         public QueryExpressionAccess (Expression expr, string methodName, TypeArguments typeArguments, Location loc)
69                                 : base (expr, methodName, typeArguments, loc)
70                         {
71                         }
72
73                         protected override Expression Error_MemberLookupFailed (Type container_type, Type qualifier_type,
74                                 Type queried_type, string name, string class_name, MemberTypes mt, BindingFlags bf)
75                         {
76                                 Report.Error (1935, loc, "An implementation of `{0}' query expression pattern could not be found. " +
77                                         "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
78                                         name);
79                                 return null;
80                         }
81                 }
82
83                 class QueryExpressionInvocation : Invocation
84                 {
85                         public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
86                                 : base (expr, arguments)
87                         {
88                         }
89
90                         protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
91                         {
92                                 int errors = Report.Errors;
93                                 MethodGroupExpr rmg = mg.OverloadResolve (ec, Arguments, true, loc);
94                                 if (rmg == null && errors == Report.Errors) {
95                                         // TODO: investigate whether would be better to re-use extension methods error handling
96                                         Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
97                                                 mg.Name, TypeManager.CSharpName (mg.Type));
98                                 }
99
100                                 return rmg;
101                         }
102                 }
103
104                 public AQueryClause next;
105                 /*protected*/ public Expression expr;
106
107                 protected AQueryClause (Expression expr, Location loc)
108                 {
109                         this.expr = expr;
110                         this.loc = loc;
111                 }
112                 
113                 protected override void CloneTo (CloneContext clonectx, Expression target)
114                 {
115                         AQueryClause t = (AQueryClause) target;
116                         if (expr != null)
117                                 t.expr = expr.Clone (clonectx);
118                         
119                         if (next != null)
120                                 t.next = (AQueryClause)next.Clone (clonectx);
121                 }
122
123                 public override Expression DoResolve (EmitContext ec)
124                 {
125                         return expr.DoResolve (ec);
126                 }
127
128                 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
129                 {
130                         ArrayList args = new ArrayList (1);
131                         args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
132                         lSide = CreateQueryExpression (lSide, args);
133                         if (next != null) {
134                                 Select s = next as Select;
135                                 if (s == null || s.IsRequired (parameter))
136                                         return next.BuildQueryClause (ec, lSide, parameter, ti);
137                                         
138                                 // Skip transparent select clause if any clause follows
139                                 if (next.next != null)
140                                         return next.next.BuildQueryClause (ec, lSide, parameter, ti);
141                         }
142
143                         return lSide;
144                 }
145
146                 protected static Parameter CreateBlockParameter (Block block)
147                 {
148                         ICollection values = block.Variables.Values;
149                         if (values.Count != 1)
150                                 throw new NotImplementedException ("Count != 1");
151
152                         IEnumerator enumerator = values.GetEnumerator ();
153                         enumerator.MoveNext ();
154                         LocalInfo li = (LocalInfo) enumerator.Current;
155
156                         return new ImplicitQueryParameter (li);
157                 }
158
159                 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
160                 {
161                         return new QueryExpressionInvocation (
162                                 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
163                 }
164
165                 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
166                 {
167                         return new QueryExpressionInvocation (
168                                 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
169                 }
170
171                 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter parameter, TransparentIdentifiersScope ti)
172                 {
173                         return CreateSelectorArgument (ec, expr, new Parameter [] { parameter }, ti);
174                 }
175
176                 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter[] parameters, TransparentIdentifiersScope ti)
177                 {
178                         Parameters p = new Parameters (parameters);
179
180                         LambdaExpression selector = new LambdaExpression (
181                                 null, null, (TypeContainer)ec.TypeContainer, p, ec.CurrentBlock, loc);
182                         selector.Block = new SelectorBlock (ec.CurrentBlock, p, ti, loc);
183                         selector.Block.AddStatement (new Return (expr, loc));
184
185                         if (!ec.IsInProbingMode) {
186                                 selector.CreateAnonymousHelpers ();
187
188                                 // TODO: I am not sure where this should be done to work
189                                 // correctly with anonymous containerss and was called only once
190                                 // FIXME: selector.RootScope == null for nested anonymous
191                                 // methods only ?
192                                 if (selector.RootScope != null)
193                                         selector.RootScope.DefineType ();
194                         }
195                         
196                         return new Argument (selector);
197                 }
198
199                 public override void Emit (EmitContext ec)
200                 {
201                         throw new NotSupportedException ();
202                 }
203
204                 protected abstract string MethodName { get; }
205
206                 public virtual AQueryClause Next {
207                         set {
208                                 next = value;
209                         }
210                 }
211
212                 public AQueryClause Tail {
213                         get {
214                                 return next == null ? this : next.Tail;
215                         }
216                 }
217         }
218
219         //
220         // A query clause with an identifier (range variable)
221         //
222         public abstract class ARangeVariableQueryClause : AQueryClause
223         {
224                 Block block;
225                 protected Expression element_selector;
226
227                 protected ARangeVariableQueryClause (Block block, Expression expr, Location loc)
228                         : base (expr, loc)
229                 {
230                         this.block = block;
231                 }
232
233                 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
234                         ref Parameter parameter, TransparentIdentifiersScope ti)
235                 {
236                         args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
237                         args.Add (CreateSelectorArgument (ec, element_selector,
238                                 new Parameter [] { parentParameter, parameter }, ti));
239                 }
240
241                 //
242                 // Customization for range variables which not only creates a lambda expression but
243                 // also builds a chain of range varible pairs
244                 //
245                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
246                 {
247                         Parameter parameter = CreateBlockParameter (block);
248
249                         if (next != null) {
250                                 //
251                                 // Builds transparent identifiers, each identifier includes its parent
252                                 // type at index 0, and new value at index 1. This is not valid for the
253                                 // first one which includes two values directly.
254                                 //
255                                 ArrayList transp_args = new ArrayList (2);
256                                 transp_args.Add (new AnonymousTypeParameter (parentParameter));
257                                 transp_args.Add (CreateAnonymousTypeVariable (parameter));
258                                 element_selector = new AnonymousTypeDeclaration (transp_args, (TypeContainer) ec.TypeContainer, loc);
259                         }
260
261                         ArrayList args = new ArrayList ();
262                         AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
263
264                         lSide = CreateQueryExpression (lSide, args);
265                         if (next != null) {
266                                 //
267                                 // Parameter identifiers go to the scope
268                                 //
269                                 string[] identifiers;
270                                 if (ti == null) {
271                                         identifiers = new string [] { parentParameter.Name, parameter.Name };
272                                 } else {
273                                         identifiers = new string [] { parameter.Name };
274                                 }
275
276                                 TransparentParameter tp = new TransparentParameter (loc);
277                                 return next.BuildQueryClause (ec, lSide, tp,
278                                         new TransparentIdentifiersScope (ti, tp, identifiers));
279                         }
280
281                         return lSide;
282                 }
283                 
284                 protected override void CloneTo (CloneContext clonectx, Expression target)
285                 {
286                         ARangeVariableQueryClause t = (ARangeVariableQueryClause) target;
287                         t.block = (Block) block.Clone (clonectx);
288                         t.element_selector = element_selector.Clone (clonectx);
289                         base.CloneTo (clonectx, t);
290                 }
291                 
292                 //
293                 // For transparent identifiers, creates an instance of variable expression
294                 //
295                 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
296                 {
297                         return new AnonymousTypeParameter (parameter);
298                 }
299         }
300
301         public class QueryStartClause : AQueryClause
302         {
303                 public QueryStartClause (Expression expr)
304                         : base (expr, expr.Location)
305                 {
306                 }
307
308                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
309                 {
310                         return next.BuildQueryClause (ec, expr, parameter, ti);
311                 }
312
313                 public override Expression DoResolve (EmitContext ec)
314                 {
315                         Expression e = BuildQueryClause (ec, null, null, null);
316                         return e.Resolve (ec);
317                 }
318
319                 protected override string MethodName {
320                         get { throw new NotSupportedException (); }
321                 }
322         }
323
324         public class Cast : QueryStartClause
325         {
326                 // We don't have to clone cast type
327                 readonly Expression type_expr;
328
329                 public Cast (Expression type, Expression expr)
330                         : base (expr)
331                 {
332                         this.type_expr = type;
333                 }
334                 
335                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
336                 {
337                         lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
338                         if (next != null)
339                                 return next.BuildQueryClause (ec, lSide, parameter, ti);
340
341                         return lSide;
342                 }
343
344                 protected override string MethodName {
345                         get { return "Cast"; }
346                 }
347         }
348
349         public class GroupBy : AQueryClause
350         {
351                 Expression element_selector;
352                 
353                 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
354                         : base (keySelector, loc)
355                 {
356                         this.element_selector = elementSelector;
357                 }
358
359                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
360                 {
361                         ArrayList args = new ArrayList (2);
362                         args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
363
364                         // A query can be optimized when selector is not group by specific
365                         if (!element_selector.Equals (lSide))
366                                 args.Add (CreateSelectorArgument (ec, element_selector, parameter, ti));
367
368                         lSide = CreateQueryExpression (lSide, args);
369                         if (next != null)
370                                 return next.BuildQueryClause (ec, lSide, parameter, ti);
371
372                         return lSide;
373                 }
374         
375                 protected override void CloneTo (CloneContext clonectx, Expression target)
376                 {
377                         GroupBy t = (GroupBy) target;
378                         t.element_selector = element_selector.Clone (clonectx);
379                         base.CloneTo (clonectx, t);
380                 }
381
382                 protected override string MethodName {
383                         get { return "GroupBy"; }
384                 }
385         }
386
387         public class Join : ARangeVariableQueryClause
388         {
389                 Expression projection;
390                 Expression inner_selector, outer_selector;
391
392                 public Join (Block block, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
393                         : base (block, inner, loc)
394                 {
395                         this.outer_selector = outerSelector;
396                         this.inner_selector = innerSelector;
397                 }
398
399                 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
400                         ref Parameter parameter, TransparentIdentifiersScope ti)
401                 {
402                         args.Add (new Argument (expr));
403                         args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
404                         args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
405
406                         parameter = CreateResultSelectorParameter (parameter);
407                         if (projection == null) {
408                                 ArrayList join_args = new ArrayList (2);
409                                 join_args.Add (new AnonymousTypeParameter (parentParameter));
410                                 join_args.Add (new AnonymousTypeParameter (parameter));
411                                 projection = new AnonymousTypeDeclaration (join_args, (TypeContainer) ec.TypeContainer, loc);
412                         }
413
414                         args.Add (CreateSelectorArgument (ec, projection,
415                                 new Parameter [] { parentParameter, parameter }, ti));
416                 }
417         
418                 protected override void CloneTo (CloneContext clonectx, Expression target)
419                 {
420                         Join t = (Join) target;
421                         t.projection = projection.Clone (clonectx);
422                         t.inner_selector = inner_selector.Clone (clonectx);
423                         t.outer_selector = outer_selector.Clone (clonectx);
424                         base.CloneTo (clonectx, t);
425                 }       
426
427                 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
428                 {
429                         return parameter;
430                 }
431
432                 public override AQueryClause Next {
433                         set {
434                                 // Use select as join projection
435                                 if (value is Select) {
436                                         projection = value.expr;
437                                         next = value.next;
438                                         return;
439                                 }
440
441                                 base.Next = value;
442                         }
443                 }
444
445                 protected override string MethodName {
446                         get { return "Join"; }
447                 }
448         }
449
450         public class GroupJoin : Join
451         {
452                 string into_variable;
453
454                 public GroupJoin (Block block, Expression inner, Expression outerSelector, Expression innerSelector,
455                         string into, Location loc)
456                         : base (block, inner, outerSelector, innerSelector, loc)
457                 {
458                         this.into_variable = into;
459                 }
460
461                 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
462                 {
463                         //
464                         // into variable is used as result selector and it's passed as
465                         // transparent identifiers to the next clause
466                         //
467                         return new ImplicitLambdaParameter (into_variable, loc);
468                 }
469
470                 protected override string MethodName {
471                         get { return "GroupJoin"; }
472                 }
473         }
474
475         public class Let : ARangeVariableQueryClause
476         {
477                 class RangeAnonymousTypeParameter : AnonymousTypeParameter
478                 {
479                         readonly Parameter parameter;
480
481                         public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
482                                 : base (initializer, parameter.Name, parameter.Location)
483                         {
484                                 this.parameter = parameter;
485                         }
486
487                         public override Expression DoResolve (EmitContext ec)
488                         {
489                                 Expression e = base.DoResolve (ec);
490                                 if (e != null) {
491                                         //
492                                         // Spread resolved initializer type
493                                         //
494                                         parameter.ParameterType = type;
495                                         parameter.Resolve (ec);
496                                 }
497
498                                 return e;
499                         }
500                 }
501
502                 public Let (Block block, Expression expr, Location loc)
503                         : base (block, expr, loc)
504                 {
505                 }
506
507                 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
508                         ref Parameter parameter, TransparentIdentifiersScope ti)
509                 {
510                         args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
511                 }
512
513                 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
514                 {
515                         return new RangeAnonymousTypeParameter (expr, parameter);
516                 }
517
518                 protected override string MethodName {
519                         get { return "Select"; }
520                 }
521         }
522
523         public class Select : AQueryClause
524         {
525                 public Select (Expression expr, Location loc)
526                         : base (expr, loc)
527                 {
528                 }
529                 
530                 //
531                 // For queries like `from a orderby a select a'
532                 // the projection is transparent and select clause can be safely removed 
533                 //
534                 public bool IsRequired (Parameter parameter)
535                 {
536                         SimpleName sn = expr as SimpleName;
537                         if (sn == null)
538                                 return true;
539
540                         return sn.Name != parameter.Name;
541                 }
542
543                 protected override string MethodName {
544                         get { return "Select"; }
545                 }
546         }
547
548         public class SelectMany : ARangeVariableQueryClause
549         {
550                 public SelectMany (Block block, Expression expr)
551                         : base (block, expr, expr.Location)
552                 {
553                 }
554
555                 protected override string MethodName {
556                         get { return "SelectMany"; }
557                 }
558
559                 public override AQueryClause Next {
560                         set {
561                                 element_selector = value.expr;
562
563                                 // Can be optimized as SelectMany element selector
564                                 if (value is Select)
565                                         return;
566
567                                 next = value;
568                         }
569                 }
570         }
571
572         public class Where : AQueryClause
573         {
574                 public Where (Expression expr, Location loc)
575                         : base (expr, loc)
576                 {
577                 }
578
579                 protected override string MethodName {
580                         get { return "Where"; }
581                 }
582         }
583
584         public class OrderByAscending : AQueryClause
585         {
586                 public OrderByAscending (Expression expr)
587                         : base (expr, expr.Location)
588                 {
589                 }
590
591                 protected override string MethodName {
592                         get { return "OrderBy"; }
593                 }
594         }
595
596         public class OrderByDescending : AQueryClause
597         {
598                 public OrderByDescending (Expression expr)
599                         : base (expr, expr.Location)
600                 {
601                 }
602
603                 protected override string MethodName {
604                         get { return "OrderByDescending"; }
605                 }
606         }
607
608         public class ThenByAscending : OrderByAscending
609         {
610                 public ThenByAscending (Expression expr)
611                         : base (expr)
612                 {
613                 }
614
615                 protected override string MethodName {
616                         get { return "ThenBy"; }
617                 }
618         }
619
620         public class ThenByDescending : OrderByDescending
621         {
622                 public ThenByDescending (Expression expr)
623                         : base (expr)
624                 {
625                 }
626
627                 protected override string MethodName {
628                         get { return "ThenByDescending"; }
629                 }
630         }
631
632         class ImplicitQueryParameter : ImplicitLambdaParameter
633         {
634                 public sealed class ImplicitType : Expression
635                 {
636                         public static ImplicitType Instance = new ImplicitType ();
637
638                         private ImplicitType ()
639                         {
640                         }
641
642                         protected override void CloneTo (CloneContext clonectx, Expression target)
643                         {
644                                 // Nothing to clone
645                         }
646
647                         public override Expression DoResolve (EmitContext ec)
648                         {
649                                 throw new NotSupportedException ();
650                         }
651
652                         public override void Emit (EmitContext ec)
653                         {
654                                 throw new NotSupportedException ();
655                         }
656                 }
657
658                 readonly LocalInfo variable;
659
660                 public ImplicitQueryParameter (LocalInfo variable)
661                         : base (variable.Name, variable.Location)
662                 {
663                         this.variable = variable;
664                 }
665
666                 public override bool Resolve (IResolveContext ec)
667                 {
668                         if (!base.Resolve (ec))
669                                 return false;
670
671                         variable.VariableType = parameter_type;
672                         return true;
673                 }
674         }
675
676         //
677         // Transparent parameters are used to package up the intermediate results
678         // and pass them onto next clause
679         //
680         public class TransparentParameter : ImplicitLambdaParameter
681         {
682                 static int counter;
683                 const string ParameterNamePrefix = "<>__TranspIdent";
684
685                 public TransparentParameter (Location loc)
686                         : base (ParameterNamePrefix + counter++, loc)
687                 {
688                 }
689         }
690
691         //
692         // Transparent identifiers are stored in nested anonymous types, each type can contain
693         // up to 2 identifiers or 1 identifier and parent type.
694         //
695         public class TransparentIdentifiersScope
696         {
697                 readonly string [] identifiers;
698                 readonly TransparentIdentifiersScope parent;
699                 readonly TransparentParameter parameter;
700
701                 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
702                         TransparentParameter parameter, string [] identifiers)
703                 {
704                         this.parent = parent;
705                         this.parameter = parameter;
706                         this.identifiers = identifiers;
707                 }
708
709                 public MemberAccess GetIdentifier (string name)
710                 {
711                         TransparentIdentifiersScope ident = FindIdentifier (name);
712                         if (ident == null)
713                                 return null;
714
715                         return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
716                 }
717
718                 TransparentIdentifiersScope FindIdentifier (string name)
719                 {
720                         foreach (string s in identifiers) {
721                                 if (s == name)
722                                         return this;
723                         }
724
725                         if (parent == null)
726                                 return null;
727
728                         return parent.FindIdentifier (name);
729                 }
730
731                 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
732                 {
733                         Expression expr = new SimpleName (parameter.Name, parameter.Location);
734                         TransparentIdentifiersScope current = this;
735                         while (current != end)
736                         {
737                                 current = current.parent;
738                                 expr = new MemberAccess (expr, current.parameter.Name);
739                         }
740
741                         return expr;
742                 }
743         }
744
745         //
746         // Lambda expression block which contains transparent identifiers
747         //
748         class SelectorBlock : ToplevelBlock
749         {
750                 readonly TransparentIdentifiersScope transparent_identifiers;
751
752                 public SelectorBlock (Block block, Parameters parameters, 
753                         TransparentIdentifiersScope transparentIdentifiers, Location loc)
754                         : base (block, parameters, loc)
755                 {
756                         this.transparent_identifiers = transparentIdentifiers;
757                 }
758
759                 public override Expression GetTransparentIdentifier (string name)
760                 {
761                         if (transparent_identifiers == null)
762                                 return null;
763
764                         return transparent_identifiers.GetIdentifier (name);
765                 }
766         }
767 }