2 // linq.cs: support for query expressions
4 // Authors: Marek Safar (marek.safar@gmail.com)
6 // Dual licensed under the terms of the MIT X11 or GNU GPL
8 // Copyright 2007-2008 Novell, Inc
9 // Copyright 2011 Xamarin Inc
13 using System.Collections.Generic;
15 namespace Mono.CSharp.Linq
17 public class QueryExpression : AQueryClause
19 public QueryExpression (AQueryClause start)
20 : base (null, null, start.Location)
25 public override Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parentParameter)
27 return next.BuildQueryClause (ec, lSide, parentParameter);
30 protected override Expression DoResolve (ResolveContext ec)
32 int counter = QueryBlock.TransparentParameter.Counter;
34 Expression e = BuildQueryClause (ec, null, null);
39 // Reset counter in probing mode to ensure that all transparent
40 // identifier anonymous types are created only once
42 if (ec.IsInProbingMode)
43 QueryBlock.TransparentParameter.Counter = counter;
48 protected override string MethodName {
49 get { throw new NotSupportedException (); }
53 public abstract class AQueryClause : ShimExpression
55 protected class QueryExpressionAccess : MemberAccess
57 public QueryExpressionAccess (Expression expr, string methodName, Location loc)
58 : base (expr, methodName, loc)
62 public QueryExpressionAccess (Expression expr, string methodName, TypeArguments typeArguments, Location loc)
63 : base (expr, methodName, typeArguments, loc)
67 protected override void Error_TypeDoesNotContainDefinition (ResolveContext ec, TypeSpec type, string name)
69 ec.Report.Error (1935, loc, "An implementation of `{0}' query expression pattern could not be found. " +
70 "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
75 protected class QueryExpressionInvocation : Invocation, OverloadResolver.IErrorHandler
77 public QueryExpressionInvocation (QueryExpressionAccess expr, Arguments arguments)
78 : base (expr, arguments)
82 protected override MethodGroupExpr DoResolveOverload (ResolveContext ec)
84 MethodGroupExpr rmg = mg.OverloadResolve (ec, ref arguments, this, OverloadResolver.Restrictions.None);
88 protected override Expression DoResolveDynamic (ResolveContext ec, Expression memberExpr)
90 ec.Report.Error (1979, loc,
91 "Query expressions with a source or join sequence of type `dynamic' are not allowed");
95 #region IErrorHandler Members
97 bool OverloadResolver.IErrorHandler.AmbiguousCandidates (ResolveContext ec, MemberSpec best, MemberSpec ambiguous)
99 ec.Report.SymbolRelatedToPreviousError (best);
100 ec.Report.SymbolRelatedToPreviousError (ambiguous);
101 ec.Report.Error (1940, loc, "Ambiguous implementation of the query pattern `{0}' for source type `{1}'",
102 best.Name, mg.InstanceExpression.GetSignatureForError ());
106 bool OverloadResolver.IErrorHandler.ArgumentMismatch (ResolveContext rc, MemberSpec best, Argument arg, int index)
111 bool OverloadResolver.IErrorHandler.NoArgumentMatch (ResolveContext rc, MemberSpec best)
116 bool OverloadResolver.IErrorHandler.TypeInferenceFailed (ResolveContext rc, MemberSpec best)
118 var ms = (MethodSpec) best;
119 TypeSpec source_type = ms.Parameters.ExtensionMethodType;
120 if (source_type != null) {
121 Argument a = arguments[0];
123 if (TypeManager.IsGenericType (source_type) && InflatedTypeSpec.ContainsTypeParameter (source_type)) {
124 TypeInferenceContext tic = new TypeInferenceContext (source_type.TypeArguments);
125 tic.OutputTypeInference (rc, a.Expr, source_type);
126 if (tic.FixAllTypes (rc)) {
127 source_type = source_type.GetDefinition ().MakeGenericType (rc, tic.InferredTypeArguments);
131 if (!Convert.ImplicitConversionExists (rc, a.Expr, source_type)) {
132 rc.Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
133 best.Name, a.Type.GetSignatureForError ());
138 if (best.Name == "SelectMany") {
139 rc.Report.Error (1943, loc,
140 "An expression type is incorrect in a subsequent `from' clause in a query expression with source type `{0}'",
141 arguments[0].GetSignatureForError ());
143 rc.Report.Error (1942, loc,
144 "An expression type in `{0}' clause is incorrect. Type inference failed in the call to `{1}'",
145 best.Name.ToLowerInvariant (), best.Name);
154 public AQueryClause next;
155 public QueryBlock block;
157 protected AQueryClause (QueryBlock block, Expression expr, Location loc)
164 protected override void CloneTo (CloneContext clonectx, Expression target)
166 base.CloneTo (clonectx, target);
168 AQueryClause t = (AQueryClause) target;
171 t.block = (QueryBlock) clonectx.LookupBlock (block);
174 t.next = (AQueryClause) next.Clone (clonectx);
177 protected override Expression DoResolve (ResolveContext ec)
179 return expr.Resolve (ec);
182 public virtual Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parameter)
184 Arguments args = null;
185 CreateArguments (ec, parameter, ref args);
186 lSide = CreateQueryExpression (lSide, args);
188 parameter = CreateChildrenParameters (parameter);
190 Select s = next as Select;
191 if (s == null || s.IsRequired (parameter))
192 return next.BuildQueryClause (ec, lSide, parameter);
194 // Skip transparent select clause if any clause follows
195 if (next.next != null)
196 return next.next.BuildQueryClause (ec, lSide, parameter);
202 protected virtual Parameter CreateChildrenParameters (Parameter parameter)
204 // Have to clone the parameter for any children use, it carries block sensitive data
205 return parameter.Clone ();
208 protected virtual void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
210 args = new Arguments (2);
212 LambdaExpression selector = new LambdaExpression (loc);
214 block.SetParameter (parameter);
215 selector.Block = block;
216 selector.Block.AddStatement (new ContextualReturn (expr));
218 args.Add (new Argument (selector));
221 protected Invocation CreateQueryExpression (Expression lSide, Arguments arguments)
223 return new QueryExpressionInvocation (
224 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
227 protected abstract string MethodName { get; }
229 public AQueryClause Next {
235 public AQueryClause Tail {
237 return next == null ? this : next.Tail;
243 // A query clause with an identifier (range variable)
245 public abstract class ARangeVariableQueryClause : AQueryClause
247 sealed class RangeAnonymousTypeParameter : AnonymousTypeParameter
249 public RangeAnonymousTypeParameter (Expression initializer, RangeVariable parameter)
250 : base (initializer, parameter.Name, parameter.Location)
254 protected override void Error_InvalidInitializer (ResolveContext ec, string initializer)
256 ec.Report.Error (1932, loc, "A range variable `{0}' cannot be initialized with `{1}'",
261 class RangeParameterReference : ParameterReference
265 public RangeParameterReference (Parameter p)
266 : base (null, p.Location)
271 protected override Expression DoResolve (ResolveContext ec)
273 pi = ec.CurrentBlock.ParametersBlock.GetParameterInfo (parameter);
274 return base.DoResolve (ec);
278 protected RangeVariable identifier;
280 protected ARangeVariableQueryClause (QueryBlock block, RangeVariable identifier, Expression expr, Location loc)
281 : base (block, expr, loc)
283 this.identifier = identifier;
286 public RangeVariable Identifier {
292 public FullNamedExpression IdentifierType { get; set; }
294 protected Invocation CreateCastExpression (Expression lSide)
296 return new QueryExpressionInvocation (
297 new QueryExpressionAccess (lSide, "Cast", new TypeArguments (IdentifierType), loc), null);
300 protected override Parameter CreateChildrenParameters (Parameter parameter)
302 return new QueryBlock.TransparentParameter (parameter.Clone (), GetIntoVariable ());
305 protected static Expression CreateRangeVariableType (ResolveContext rc, Parameter parameter, RangeVariable name, Expression init)
307 var args = new List<AnonymousTypeParameter> (2);
310 // The first argument is the reference to the parameter
312 args.Add (new AnonymousTypeParameter (new RangeParameterReference (parameter), parameter.Name, parameter.Location));
315 // The second argument is the linq expression
317 args.Add (new RangeAnonymousTypeParameter (init, name));
320 // Create unique anonymous type
322 return new NewAnonymousType (args, rc.MemberContext.CurrentMemberDefinition.Parent, name.Location);
325 protected virtual RangeVariable GetIntoVariable ()
331 public sealed class RangeVariable : INamedBlockVariable
335 public RangeVariable (string name, Location loc)
352 public bool IsDeclared {
358 public bool IsParameter {
364 public Location Location { get; private set; }
366 public string Name { get; private set; }
370 public Expression CreateReferenceExpression (ResolveContext rc, Location loc)
373 // We know the variable name is somewhere in the scope. This generates
374 // an access expression from current block
376 var pb = rc.CurrentBlock.ParametersBlock;
378 if (pb is QueryBlock) {
379 for (int i = pb.Parameters.Count - 1; i >= 0; --i) {
380 var p = pb.Parameters[i];
382 return pb.GetParameterReference (i, loc);
384 Expression expr = null;
385 var tp = p as QueryBlock.TransparentParameter;
388 expr = pb.GetParameterReference (i, loc);
390 expr = new TransparentMemberAccess (expr, tp.Name);
392 if (tp.Identifier == Name)
393 return new TransparentMemberAccess (expr, Name);
395 if (tp.Parent.Name == Name)
396 return new TransparentMemberAccess (expr, Name);
398 tp = tp.Parent as QueryBlock.TransparentParameter;
406 pb = pb.Parent.ParametersBlock;
411 public class QueryStartClause : ARangeVariableQueryClause
413 public QueryStartClause (QueryBlock block, Expression expr, RangeVariable identifier, Location loc)
414 : base (block, identifier, expr, loc)
416 block.AddRangeVariable (identifier);
419 public override Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parameter)
421 if (IdentifierType != null)
422 expr = CreateCastExpression (expr);
424 if (parameter == null)
427 return next.BuildQueryClause (ec, lSide, new ImplicitLambdaParameter (identifier.Name, identifier.Location));
430 protected override Expression DoResolve (ResolveContext ec)
432 Expression e = BuildQueryClause (ec, null, null);
433 return e.Resolve (ec);
436 protected override string MethodName {
437 get { throw new NotSupportedException (); }
441 public class GroupBy : AQueryClause
443 Expression element_selector;
444 QueryBlock element_block;
446 public GroupBy (QueryBlock block, Expression elementSelector, QueryBlock elementBlock, Expression keySelector, Location loc)
447 : base (block, keySelector, loc)
450 // Optimizes clauses like `group A by A'
452 if (!elementSelector.Equals (keySelector)) {
453 this.element_selector = elementSelector;
454 this.element_block = elementBlock;
458 public Expression SelectorExpression {
460 return element_selector;
464 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
466 base.CreateArguments (ec, parameter, ref args);
468 if (element_selector != null) {
469 LambdaExpression lambda = new LambdaExpression (element_selector.Location);
471 element_block.SetParameter (parameter.Clone ());
472 lambda.Block = element_block;
473 lambda.Block.AddStatement (new ContextualReturn (element_selector));
474 args.Add (new Argument (lambda));
478 protected override void CloneTo (CloneContext clonectx, Expression target)
480 GroupBy t = (GroupBy) target;
481 if (element_selector != null) {
482 t.element_selector = element_selector.Clone (clonectx);
483 t.element_block = (QueryBlock) element_block.Clone (clonectx);
486 base.CloneTo (clonectx, t);
489 protected override string MethodName {
490 get { return "GroupBy"; }
493 public override object Accept (StructuralVisitor visitor)
495 return visitor.Visit (this);
499 public class Join : SelectMany
501 QueryBlock inner_selector, outer_selector;
503 public Join (QueryBlock block, RangeVariable lt, Expression inner, QueryBlock outerSelector, QueryBlock innerSelector, Location loc)
504 : base (block, lt, inner, loc)
506 this.outer_selector = outerSelector;
507 this.inner_selector = innerSelector;
510 public QueryBlock InnerSelector {
512 return inner_selector;
516 public QueryBlock OuterSelector {
518 return outer_selector;
522 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
524 args = new Arguments (4);
526 if (IdentifierType != null)
527 expr = CreateCastExpression (expr);
529 args.Add (new Argument (expr));
531 outer_selector.SetParameter (parameter.Clone ());
532 var lambda = new LambdaExpression (outer_selector.StartLocation);
533 lambda.Block = outer_selector;
534 args.Add (new Argument (lambda));
536 inner_selector.SetParameter (new ImplicitLambdaParameter (identifier.Name, identifier.Location));
537 lambda = new LambdaExpression (inner_selector.StartLocation);
538 lambda.Block = inner_selector;
539 args.Add (new Argument (lambda));
541 base.CreateArguments (ec, parameter, ref args);
544 protected override void CloneTo (CloneContext clonectx, Expression target)
546 Join t = (Join) target;
547 t.inner_selector = (QueryBlock) inner_selector.Clone (clonectx);
548 t.outer_selector = (QueryBlock) outer_selector.Clone (clonectx);
549 base.CloneTo (clonectx, t);
552 protected override string MethodName {
553 get { return "Join"; }
556 public override object Accept (StructuralVisitor visitor)
558 return visitor.Visit (this);
562 public class GroupJoin : Join
564 readonly RangeVariable into;
566 public GroupJoin (QueryBlock block, RangeVariable lt, Expression inner,
567 QueryBlock outerSelector, QueryBlock innerSelector, RangeVariable into, Location loc)
568 : base (block, lt, inner, outerSelector, innerSelector, loc)
573 protected override RangeVariable GetIntoVariable ()
578 protected override string MethodName {
579 get { return "GroupJoin"; }
582 public override object Accept (StructuralVisitor visitor)
584 return visitor.Visit (this);
588 public class Let : ARangeVariableQueryClause
590 public Let (QueryBlock block, RangeVariable identifier, Expression expr, Location loc)
591 : base (block, identifier, expr, loc)
595 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
597 expr = CreateRangeVariableType (ec, parameter, identifier, expr);
598 base.CreateArguments (ec, parameter, ref args);
601 protected override string MethodName {
602 get { return "Select"; }
605 public override object Accept (StructuralVisitor visitor)
607 return visitor.Visit (this);
611 public class Select : AQueryClause
613 public Select (QueryBlock block, Expression expr, Location loc)
614 : base (block, expr, loc)
619 // For queries like `from a orderby a select a'
620 // the projection is transparent and select clause can be safely removed
622 public bool IsRequired (Parameter parameter)
624 SimpleName sn = expr as SimpleName;
628 return sn.Name != parameter.Name;
631 protected override string MethodName {
632 get { return "Select"; }
635 public override object Accept (StructuralVisitor visitor)
637 return visitor.Visit (this);
642 public class SelectMany : ARangeVariableQueryClause
644 public SelectMany (QueryBlock block, RangeVariable identifier, Expression expr, Location loc)
645 : base (block, identifier, expr, loc)
649 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
652 if (IdentifierType != null)
653 expr = CreateCastExpression (expr);
655 base.CreateArguments (ec, parameter.Clone (), ref args);
658 Expression result_selector_expr;
659 QueryBlock result_block;
661 var target = GetIntoVariable ();
662 var target_param = new ImplicitLambdaParameter (target.Name, target.Location);
665 // When select follows use it as a result selector
667 if (next is Select) {
668 result_selector_expr = next.Expr;
670 result_block = next.block;
671 result_block.SetParameters (parameter, target_param);
675 result_selector_expr = CreateRangeVariableType (ec, parameter, target, new SimpleName (target.Name, target.Location));
677 result_block = new QueryBlock (block.Parent, block.StartLocation);
678 result_block.SetParameters (parameter, target_param);
681 LambdaExpression result_selector = new LambdaExpression (Location);
682 result_selector.Block = result_block;
683 result_selector.Block.AddStatement (new ContextualReturn (result_selector_expr));
685 args.Add (new Argument (result_selector));
688 protected override string MethodName {
689 get { return "SelectMany"; }
692 public override object Accept (StructuralVisitor visitor)
694 return visitor.Visit (this);
698 public class Where : AQueryClause
700 public Where (QueryBlock block, Expression expr, Location loc)
701 : base (block, expr, loc)
705 protected override string MethodName {
706 get { return "Where"; }
709 public override object Accept (StructuralVisitor visitor)
711 return visitor.Visit (this);
715 public class OrderByAscending : AQueryClause
717 public OrderByAscending (QueryBlock block, Expression expr)
718 : base (block, expr, expr.Location)
722 protected override string MethodName {
723 get { return "OrderBy"; }
726 public override object Accept (StructuralVisitor visitor)
728 return visitor.Visit (this);
732 public class OrderByDescending : AQueryClause
734 public OrderByDescending (QueryBlock block, Expression expr)
735 : base (block, expr, expr.Location)
739 protected override string MethodName {
740 get { return "OrderByDescending"; }
743 public override object Accept (StructuralVisitor visitor)
745 return visitor.Visit (this);
749 public class ThenByAscending : OrderByAscending
751 public ThenByAscending (QueryBlock block, Expression expr)
756 protected override string MethodName {
757 get { return "ThenBy"; }
760 public override object Accept (StructuralVisitor visitor)
762 return visitor.Visit (this);
766 public class ThenByDescending : OrderByDescending
768 public ThenByDescending (QueryBlock block, Expression expr)
773 protected override string MethodName {
774 get { return "ThenByDescending"; }
777 public override object Accept (StructuralVisitor visitor)
779 return visitor.Visit (this);
784 // Implicit query block
786 public class QueryBlock : ParametersBlock
789 // Transparent parameters are used to package up the intermediate results
790 // and pass them onto next clause
792 public sealed class TransparentParameter : ImplicitLambdaParameter
794 public static int Counter;
795 const string ParameterNamePrefix = "<>__TranspIdent";
797 public readonly Parameter Parent;
798 public readonly string Identifier;
800 public TransparentParameter (Parameter parent, RangeVariable identifier)
801 : base (ParameterNamePrefix + Counter++, identifier.Location)
804 Identifier = identifier.Name;
807 public static void Reset ()
813 public QueryBlock (Block parent, Location start)
814 : base (parent, ParametersCompiled.EmptyReadOnlyParameters, start, Flags.CompilerGenerated)
818 public void AddRangeVariable (RangeVariable variable)
820 variable.Block = this;
821 TopBlock.AddLocalName (variable.Name, variable, true);
824 public override void Error_AlreadyDeclared (string name, INamedBlockVariable variable, string reason)
826 TopBlock.Report.Error (1931, variable.Location,
827 "A range variable `{0}' conflicts with a previous declaration of `{0}'",
831 public override void Error_AlreadyDeclared (string name, INamedBlockVariable variable)
833 TopBlock.Report.Error (1930, variable.Location,
834 "A range variable `{0}' has already been declared in this scope",
838 public override void Error_AlreadyDeclaredTypeParameter (string name, Location loc)
840 TopBlock.Report.Error (1948, loc,
841 "A range variable `{0}' conflicts with a method type parameter",
845 public void SetParameter (Parameter parameter)
847 base.parameters = new ParametersCompiled (parameter);
848 base.parameter_info = new ParameterInfo[] {
849 new ParameterInfo (this, 0)
853 public void SetParameters (Parameter first, Parameter second)
855 base.parameters = new ParametersCompiled (first, second);
856 base.parameter_info = new ParameterInfo[] {
857 new ParameterInfo (this, 0),
858 new ParameterInfo (this, 1)
863 sealed class TransparentMemberAccess : MemberAccess
865 public TransparentMemberAccess (Expression expr, string name)
870 public override Expression DoResolveLValue (ResolveContext rc, Expression right_side)
872 rc.Report.Error (1947, loc,
873 "A range variable `{0}' cannot be assigned to. Consider using `let' clause to store the value",