2009-01-29 Marek Safar <marek.safar@gmail.com>
[mono.git] / mcs / mcs / linq.cs
1 //
2 // linq.cs: support for query expressions
3 //
4 // Authors: Marek Safar (marek.safar@gmail.com)
5 //
6 // Dual licensed under the terms of the MIT X11 or GNU GPL
7 //
8 // Copyright 2007-2008 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         class QueryExpression : AQueryClause
24         {
25                 public QueryExpression (Block block, AQueryClause query)
26                         : base (null, null, query.Location)
27                 {
28                         this.next = query;
29                 }
30
31                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide)
32                 {
33                         return next.BuildQueryClause (ec, lSide);
34                 }
35
36                 public override Expression DoResolve (EmitContext ec)
37                 {
38                         int counter = QueryBlock.TransparentParameter.Counter;
39
40                         Expression e = BuildQueryClause (ec, null);
41                         e = e.Resolve (ec);
42
43                         //
44                         // Reset counter in probing mode to ensure that all transparent
45                         // identifier anonymous types are created only once
46                         //
47                         if (ec.IsInProbingMode)
48                                 QueryBlock.TransparentParameter.Counter = counter;
49
50                         return e;
51                 }
52
53                 protected override string MethodName {
54                         get { throw new NotSupportedException (); }
55                 }
56         }
57
58         abstract class AQueryClause : Expression
59         {
60                 class QueryExpressionAccess : MemberAccess
61                 {
62                         public QueryExpressionAccess (Expression expr, string methodName, Location loc)
63                                 : base (expr, methodName, loc)
64                         {
65                         }
66
67                         public QueryExpressionAccess (Expression expr, string methodName, TypeArguments typeArguments, Location loc)
68                                 : base (expr, methodName, typeArguments, loc)
69                         {
70                         }
71
72                         protected override Expression Error_MemberLookupFailed (Type container_type, Type qualifier_type,
73                                 Type queried_type, string name, string class_name, MemberTypes mt, BindingFlags bf)
74                         {
75                                 Report.Error (1935, loc, "An implementation of `{0}' query expression pattern could not be found. " +
76                                         "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
77                                         name);
78                                 return null;
79                         }
80                 }
81
82                 class QueryExpressionInvocation : Invocation, MethodGroupExpr.IErrorHandler
83                 {
84                         public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
85                                 : base (expr, arguments)
86                         {
87                         }
88
89                         protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
90                         {
91                                 mg.CustomErrorHandler = this;
92                                 MethodGroupExpr rmg = mg.OverloadResolve (ec, ref Arguments, false, loc);
93                                 return rmg;
94                         }
95
96                         public bool AmbiguousCall (MethodBase ambiguous)
97                         {
98                                 Report.SymbolRelatedToPreviousError ((MethodInfo) mg);
99                                 Report.SymbolRelatedToPreviousError (ambiguous);
100                                 Report.Error (1940, loc, "Ambiguous implementation of the query pattern `{0}' for source type `{1}'",
101                                         mg.Name, mg.InstanceExpression.GetSignatureForError ());
102                                 return true;
103                         }
104
105                         public bool NoExactMatch (EmitContext ec, MethodBase method)
106                         {
107 #if GMCS_SOURCE                         
108                                 AParametersCollection pd = TypeManager.GetParameterData (method);
109                                 Type source_type = pd.ExtensionMethodType;
110                                 if (source_type != null) {
111                                         Argument a = (Argument) Arguments [0];
112
113                                         if (source_type.IsGenericType && source_type.ContainsGenericParameters) {
114                                                 TypeInferenceContext tic = new TypeInferenceContext (source_type.GetGenericArguments ());
115                                                 tic.OutputTypeInference (ec, a.Expr, source_type);
116                                                 if (tic.FixAllTypes ()) {
117                                                         source_type = source_type.GetGenericTypeDefinition ().MakeGenericType (tic.InferredTypeArguments);
118                                                 }
119                                         }
120
121                                         if (!Convert.ImplicitConversionExists (ec, a.Expr, source_type)) {
122                                                 Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
123                                                         mg.Name, TypeManager.CSharpName (a.Type));
124                                                 return true;
125                                         }
126                                 }
127
128                                 if (!method.IsGenericMethod)
129                                         return false;
130
131                                 if (mg.Name == "SelectMany") {
132                                         Report.Error (1943, loc,
133                                                 "An expression type is incorrect in a subsequent `from' clause in a query expression with source type `{0}'",
134                                                 ((Argument) Arguments [0]).GetSignatureForError ());
135                                 } else {
136                                         Report.Error (1942, loc,
137                                                 "An expression type in `{0}' clause is incorrect. Type inference failed in the call to `{1}'",
138                                                 mg.Name.ToLower (), mg.Name);
139                                 }
140                                 return true;
141 #else
142                                 return false;
143 #endif
144                         }
145                 }
146
147                 // TODO: protected
148                 public AQueryClause next;
149                 public Expression expr;
150                 protected ToplevelBlock block;
151
152                 protected AQueryClause (ToplevelBlock block, Expression expr, Location loc)
153                 {
154                         this.block = block;
155                         this.expr = expr;
156                         this.loc = loc;
157                 }
158                 
159                 protected override void CloneTo (CloneContext clonectx, Expression target)
160                 {
161                         AQueryClause t = (AQueryClause) target;
162                         if (expr != null)
163                                 t.expr = expr.Clone (clonectx);
164
165                         if (block != null)
166                                 t.block = (ToplevelBlock) block.Clone (clonectx);
167
168                         if (next != null)
169                                 t.next = (AQueryClause) next.Clone (clonectx);
170                 }
171
172                 public override Expression CreateExpressionTree (EmitContext ec)
173                 {
174                         // Should not be reached
175                         throw new NotSupportedException ("ET");
176                 }
177
178                 public override Expression DoResolve (EmitContext ec)
179                 {
180                         return expr.DoResolve (ec);
181                 }
182
183                 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide)
184                 {
185                         ArrayList args;
186                         CreateArguments (ec, out args);
187                         lSide = CreateQueryExpression (lSide, args);
188                         if (next != null) {
189                                 Select s = next as Select;
190                                 if (s == null || s.IsRequired)
191                                         return next.BuildQueryClause (ec, lSide);
192                                         
193                                 // Skip transparent select clause if any clause follows
194                                 if (next.next != null)
195                                         return next.next.BuildQueryClause (ec, lSide);
196                         }
197
198                         return lSide;
199                 }
200
201                 protected virtual void CreateArguments (EmitContext ec, out ArrayList args)
202                 {
203                         args = new ArrayList (2);
204
205                         LambdaExpression selector = new LambdaExpression (loc);
206                         selector.Block = block;
207                         selector.Block.AddStatement (new ContextualReturn (expr));
208
209                         args.Add (new Argument (selector));
210                 }
211
212                 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
213                 {
214                         return new QueryExpressionInvocation (
215                                 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
216                 }
217
218                 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
219                 {
220                         return new QueryExpressionInvocation (
221                                 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
222                 }
223
224                 public override void Emit (EmitContext ec)
225                 {
226                         throw new NotSupportedException ();
227                 }
228
229                 protected abstract string MethodName { get; }
230
231                 public override void MutateHoistedGenericType (AnonymousMethodStorey storey)
232                 {
233                         // Nothing to mutate
234                 }
235
236                 public virtual AQueryClause Next {
237                         set {
238                                 next = value;
239                         }
240                 }
241
242                 public AQueryClause Tail {
243                         get {
244                                 return next == null ? this : next.Tail;
245                         }
246                 }
247         }
248
249         //
250         // A query clause with an identifier (range variable)
251         //
252         abstract class ARangeVariableQueryClause : AQueryClause
253         {
254                 sealed class RangeAnonymousTypeParameter : AnonymousTypeParameter
255                 {
256                         public RangeAnonymousTypeParameter (Expression initializer, LocatedToken parameter)
257                                 : base (initializer, parameter.Value, parameter.Location)
258                         {
259                         }
260
261                         protected override void Error_InvalidInitializer (string initializer)
262                         {
263                                 Report.Error (1932, loc, "A range variable `{0}' cannot be initialized with `{1}'",
264                                         Name, initializer);
265                         }
266                 }
267
268                 protected ARangeVariableQueryClause (ToplevelBlock block, Expression expr)
269                         : base (block, expr, expr.Location)
270                 {
271                 }
272
273                 protected static Expression CreateRangeVariableType (ToplevelBlock block, TypeContainer container, LocatedToken name, Expression init)
274                 {
275                         ArrayList args = new ArrayList (2);
276                         args.Add (new AnonymousTypeParameter (block.Parameters [0]));
277                         args.Add (new RangeAnonymousTypeParameter (init, name));
278                         return new AnonymousTypeDeclaration (args, container, name.Location);
279                 }
280         }
281
282         class QueryStartClause : AQueryClause
283         {
284                 public QueryStartClause (Expression expr)
285                         : base (null, expr, expr.Location)
286                 {
287                 }
288
289                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide)
290                 {
291                         return next.BuildQueryClause (ec, expr);
292                 }
293
294                 public override Expression DoResolve (EmitContext ec)
295                 {
296                         Expression e = BuildQueryClause (ec, null);
297                         return e.Resolve (ec);
298                 }
299
300                 protected override string MethodName {
301                         get { throw new NotSupportedException (); }
302                 }
303         }
304
305         class Cast : QueryStartClause
306         {
307                 // We don't have to clone cast type
308                 readonly FullNamedExpression type_expr;
309
310                 public Cast (FullNamedExpression type, Expression expr)
311                         : base (expr)
312                 {
313                         this.type_expr = type;
314                 }
315                 
316                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide)
317                 {
318                         lSide = CreateQueryExpression (expr, new TypeArguments (type_expr), null);
319                         if (next != null)
320                                 return next.BuildQueryClause (ec, lSide);
321
322                         return lSide;
323                 }
324
325                 protected override string MethodName {
326                         get { return "Cast"; }
327                 }
328         }
329
330         class GroupBy : AQueryClause
331         {
332                 Expression element_selector;
333                 ToplevelBlock element_block;
334                 
335                 public GroupBy (ToplevelBlock block, Expression elementSelector, ToplevelBlock elementBlock, Expression keySelector, Location loc)
336                         : base (block, keySelector, loc)
337                 {
338                         //
339                         // Optimizes clauses like `group A by A'
340                         //
341                         if (!elementSelector.Equals (keySelector)) {
342                                 this.element_selector = elementSelector;
343                                 this.element_block = elementBlock;
344                         }
345                 }
346
347                 protected override void CreateArguments (EmitContext ec, out ArrayList args)
348                 {
349                         base.CreateArguments (ec, out args);
350
351                         if (element_selector != null) {
352                                 LambdaExpression lambda = new LambdaExpression (element_selector.Location);
353                                 lambda.Block = element_block;
354                                 lambda.Block.AddStatement (new ContextualReturn (element_selector));
355                                 args.Add (new Argument (lambda));
356                         }
357                 }
358
359                 protected override void CloneTo (CloneContext clonectx, Expression target)
360                 {
361                         GroupBy t = (GroupBy) target;
362                         if (element_selector != null) {
363                                 t.element_selector = element_selector.Clone (clonectx);
364                                 t.element_block = (ToplevelBlock) element_block.Clone (clonectx);
365                         }
366
367                         base.CloneTo (clonectx, t);
368                 }
369
370                 protected override string MethodName {
371                         get { return "GroupBy"; }
372                 }
373         }
374
375         class Join : ARangeVariableQueryClause
376         {
377                 readonly LocatedToken lt;
378                 ToplevelBlock inner_selector, outer_selector;
379
380                 public Join (ToplevelBlock block, LocatedToken lt, Expression inner, ToplevelBlock outerSelector, ToplevelBlock innerSelector, Location loc)
381                         : base (block, inner)
382                 {
383                         this.lt = lt;
384                         this.outer_selector = outerSelector;
385                         this.inner_selector = innerSelector;
386                 }
387
388                 protected override void CreateArguments (EmitContext ec, out ArrayList args)
389                 {
390                         args = new ArrayList (4);
391
392                         args.Add (new Argument (expr));
393
394                         LambdaExpression lambda = new LambdaExpression (outer_selector.StartLocation);
395                         lambda.Block = outer_selector;
396                         args.Add (new Argument (lambda));
397
398                         lambda = new LambdaExpression (inner_selector.StartLocation);
399                         lambda.Block = inner_selector;
400                         args.Add (new Argument (lambda));
401
402                         Expression result_selector_expr;
403                         LocatedToken into_variable = GetIntoVariable ();
404                         //
405                         // When select follows use is as result selector
406                         //
407                         if (next is Select) {
408                                 result_selector_expr = next.expr;
409                                 next = next.next;
410                         } else {
411                                 result_selector_expr = CreateRangeVariableType (block, (TypeContainer) ec.TypeContainer, into_variable,
412                                         new SimpleName (into_variable.Value, into_variable.Location));
413                         }
414
415                         LambdaExpression result_selector = new LambdaExpression (lt.Location);
416                         result_selector.Block = new QueryBlock (block.Parent, block.Parameters, into_variable, block.StartLocation);
417                         result_selector.Block.AddStatement (new ContextualReturn (result_selector_expr));
418
419                         args.Add (new Argument (result_selector));
420                 }
421
422                 protected virtual LocatedToken GetIntoVariable ()
423                 {
424                         return lt;
425                 }
426
427                 protected override void CloneTo (CloneContext clonectx, Expression target)
428                 {
429                         Join t = (Join) target;
430                         t.inner_selector = (ToplevelBlock) inner_selector.Clone (clonectx);
431                         t.outer_selector = (ToplevelBlock) outer_selector.Clone (clonectx);
432                         base.CloneTo (clonectx, t);
433                 }       
434
435                 protected override string MethodName {
436                         get { return "Join"; }
437                 }
438         }
439
440         class GroupJoin : Join
441         {
442                 readonly LocatedToken into;
443
444                 public GroupJoin (ToplevelBlock block, LocatedToken lt, Expression inner,
445                         ToplevelBlock outerSelector, ToplevelBlock innerSelector, LocatedToken into, Location loc)
446                         : base (block, lt, inner, outerSelector, innerSelector, loc)
447                 {
448                         this.into = into;
449                 }
450
451                 protected override LocatedToken GetIntoVariable ()
452                 {
453                         return into;
454                 }
455
456                 protected override string MethodName {
457                         get { return "GroupJoin"; }
458                 }
459         }
460
461         class Let : ARangeVariableQueryClause
462         {
463                 public Let (ToplevelBlock block, TypeContainer container, LocatedToken identifier, Expression expr)
464                         : base (block, CreateRangeVariableType (block, container, identifier, expr))
465                 {
466                 }
467
468                 protected override string MethodName {
469                         get { return "Select"; }
470                 }
471         }
472
473         class Select : AQueryClause
474         {
475                 public Select (ToplevelBlock block, Expression expr, Location loc)
476                         : base (block, expr, loc)
477                 {
478                 }
479                 
480                 //
481                 // For queries like `from a orderby a select a'
482                 // the projection is transparent and select clause can be safely removed 
483                 //
484                 public bool IsRequired {
485                         get {
486                                 SimpleName sn = expr as SimpleName;
487                                 if (sn == null)
488                                         return true;
489
490                                 return sn.Name != block.Parameters.FixedParameters [0].Name;
491                         }
492                 }
493
494                 protected override string MethodName {
495                         get { return "Select"; }
496                 }
497         }
498
499         class SelectMany : ARangeVariableQueryClause
500         {
501                 LocatedToken lt;
502
503                 public SelectMany (ToplevelBlock block, LocatedToken lt, Expression expr)
504                         : base (block, expr)
505                 {
506                         this.lt = lt;
507                 }
508
509                 protected override void CreateArguments (EmitContext ec, out ArrayList args)
510                 {
511                         base.CreateArguments (ec, out args);
512
513                         Expression result_selector_expr;
514                         //
515                         // When select follow use is as result selector
516                         //
517                         if (next is Select) {
518                                 result_selector_expr = next.expr;
519                                 next = next.next;
520                         } else {
521                                 result_selector_expr = CreateRangeVariableType (block, (TypeContainer)ec.TypeContainer, lt, new SimpleName (lt.Value, lt.Location));
522                         }
523
524                         LambdaExpression result_selector = new LambdaExpression (lt.Location);
525                         result_selector.Block = new QueryBlock (block.Parent, block.Parameters, lt, block.StartLocation);
526                         result_selector.Block.AddStatement (new ContextualReturn (result_selector_expr));
527
528                         args.Add (new Argument (result_selector));
529                 }
530
531                 protected override string MethodName {
532                         get { return "SelectMany"; }
533                 }
534         }
535
536         class Where : AQueryClause
537         {
538                 public Where (ToplevelBlock block, Expression expr, Location loc)
539                         : base (block, expr, loc)
540                 {
541                 }
542
543                 protected override string MethodName {
544                         get { return "Where"; }
545                 }
546         }
547
548         class OrderByAscending : AQueryClause
549         {
550                 public OrderByAscending (ToplevelBlock block,Expression expr)
551                         : base (block, expr, expr.Location)
552                 {
553                 }
554
555                 protected override string MethodName {
556                         get { return "OrderBy"; }
557                 }
558         }
559
560         class OrderByDescending : AQueryClause
561         {
562                 public OrderByDescending (ToplevelBlock block, Expression expr)
563                         : base (block, expr, expr.Location)
564                 {
565                 }
566
567                 protected override string MethodName {
568                         get { return "OrderByDescending"; }
569                 }
570         }
571
572         class ThenByAscending : OrderByAscending
573         {
574                 public ThenByAscending (ToplevelBlock block, Expression expr)
575                         : base (block, expr)
576                 {
577                 }
578
579                 protected override string MethodName {
580                         get { return "ThenBy"; }
581                 }
582         }
583
584         class ThenByDescending : OrderByDescending
585         {
586                 public ThenByDescending (ToplevelBlock block, Expression expr)
587                         : base (block, expr)
588                 {
589                 }
590
591                 protected override string MethodName {
592                         get { return "ThenByDescending"; }
593                 }
594         }
595
596         //
597         // Implicit query block
598         //
599         class QueryBlock : ToplevelBlock
600         {
601                 //
602                 // Transparent parameters are used to package up the intermediate results
603                 // and pass them onto next clause
604                 //
605                 public sealed class TransparentParameter : ImplicitLambdaParameter
606                 {
607                         public static int Counter;
608                         const string ParameterNamePrefix = "<>__TranspIdent";
609
610                         public readonly ParametersCompiled Parent;
611                         public readonly string Identifier;
612
613                         public TransparentParameter (ParametersCompiled parent, LocatedToken identifier)
614                                 : base (ParameterNamePrefix + Counter++, identifier.Location)
615                         {
616                                 Parent = parent;
617                                 Identifier = identifier.Value;
618                         }
619
620                         public static void Reset ()
621                         {
622                                 Counter = 0;
623                         }
624                 }
625
626                 public sealed class ImplicitQueryParameter : ImplicitLambdaParameter
627                 {
628                         public ImplicitQueryParameter (string name, Location loc)
629                                 : base (name, loc)
630                         {
631                         }
632                 }
633
634                 public QueryBlock (Block parent, LocatedToken lt, Location start)
635                         : base (parent, new ParametersCompiled (new ImplicitQueryParameter (lt.Value, lt.Location)), start)
636                 {
637                         if (parent != null)
638                                 base.CheckParentConflictName (parent.Toplevel, lt.Value, lt.Location);
639                 }
640
641                 public QueryBlock (Block parent, ParametersCompiled parameters, LocatedToken lt, Location start)
642                         : base (parent, new ParametersCompiled (parameters [0].Clone (), new ImplicitQueryParameter (lt.Value, lt.Location)), start)
643                 {
644                 }
645
646                 public QueryBlock (Block parent, Location start)
647                         : base (parent, parent.Toplevel.Parameters.Clone (), start)
648                 {
649                 }
650
651                 public void AddTransparentParameter (LocatedToken name)
652                 {
653                         base.CheckParentConflictName (this, name.Value, name.Location);
654
655                         parameters = new ParametersCompiled (new TransparentParameter (parameters, name));
656                 }
657
658                 protected override bool CheckParentConflictName (ToplevelBlock block, string name, Location l)
659                 {
660                         return true;
661                 }
662
663                 // 
664                 // Query parameter reference can include transparent parameters
665                 //
666                 protected override Expression GetParameterReferenceExpression (string name, Location loc)
667                 {
668                         Expression expr = base.GetParameterReferenceExpression (name, loc);
669                         if (expr != null)
670                                 return expr;
671
672                         TransparentParameter tp = parameters [0] as TransparentParameter;
673                         while (tp != null) {
674                                 if (tp.Identifier == name)
675                                         break;
676
677                                 TransparentParameter tp_next = tp.Parent [0] as TransparentParameter;
678                                 if (tp_next == null) {
679                                         if (tp.Parent.GetParameterIndexByName (name) >= 0)
680                                                 break;
681                                 }
682
683                                 tp = tp_next;
684                         }
685
686                         if (tp != null) {
687                                 expr = new SimpleName (parameters[0].Name, loc);
688                                 TransparentParameter tp_cursor = (TransparentParameter) parameters[0];
689                                 while (tp_cursor != tp) {
690                                         tp_cursor = (TransparentParameter) tp_cursor.Parent[0];
691                                         expr = new MemberAccess (expr, tp_cursor.Name);
692                                 }
693
694                                 return new MemberAccess (expr, name);
695                         }
696
697                         return null;
698                 }
699
700                 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
701                 {
702                         Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
703                                 var);
704                 }
705                 
706                 protected override void Error_AlreadyDeclared (Location loc, string var)
707                 {
708                         Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
709                                 var);           
710                 }
711                 
712                 public override void Error_AlreadyDeclaredTypeParameter (Location loc, string name, string conflict)
713                 {
714                         Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",
715                                 name);
716                 }
717         }
718 }