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
12 using System.Collections.Generic;
14 namespace Mono.CSharp.Linq
16 public class QueryExpression : AQueryClause
18 public QueryExpression (AQueryClause start)
19 : base (null, null, Location.Null)
24 public override Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parentParameter)
26 return next.BuildQueryClause (ec, lSide, parentParameter);
29 protected override Expression DoResolve (ResolveContext ec)
31 int counter = QueryBlock.TransparentParameter.Counter;
33 Expression e = BuildQueryClause (ec, null, null);
38 // Reset counter in probing mode to ensure that all transparent
39 // identifier anonymous types are created only once
41 if (ec.IsInProbingMode)
42 QueryBlock.TransparentParameter.Counter = counter;
47 protected override string MethodName {
48 get { throw new NotSupportedException (); }
52 public abstract class AQueryClause : ShimExpression
54 protected class QueryExpressionAccess : MemberAccess
56 public QueryExpressionAccess (Expression expr, string methodName, Location loc)
57 : base (expr, methodName, loc)
61 public QueryExpressionAccess (Expression expr, string methodName, TypeArguments typeArguments, Location loc)
62 : base (expr, methodName, typeArguments, loc)
66 protected override void Error_TypeDoesNotContainDefinition (ResolveContext ec, TypeSpec type, string name)
68 ec.Report.Error (1935, loc, "An implementation of `{0}' query expression pattern could not be found. " +
69 "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
74 protected class QueryExpressionInvocation : Invocation, OverloadResolver.IErrorHandler
76 public QueryExpressionInvocation (QueryExpressionAccess expr, Arguments arguments)
77 : base (expr, arguments)
81 protected override MethodGroupExpr DoResolveOverload (ResolveContext ec)
83 MethodGroupExpr rmg = mg.OverloadResolve (ec, ref arguments, this, OverloadResolver.Restrictions.None);
87 #region IErrorHandler Members
89 bool OverloadResolver.IErrorHandler.AmbiguousCandidates (ResolveContext ec, MemberSpec best, MemberSpec ambiguous)
91 ec.Report.SymbolRelatedToPreviousError (best);
92 ec.Report.SymbolRelatedToPreviousError (ambiguous);
93 ec.Report.Error (1940, loc, "Ambiguous implementation of the query pattern `{0}' for source type `{1}'",
94 best.Name, mg.InstanceExpression.GetSignatureForError ());
98 bool OverloadResolver.IErrorHandler.ArgumentMismatch (ResolveContext rc, MemberSpec best, Argument arg, int index)
103 bool OverloadResolver.IErrorHandler.NoArgumentMatch (ResolveContext rc, MemberSpec best)
108 bool OverloadResolver.IErrorHandler.TypeInferenceFailed (ResolveContext rc, MemberSpec best)
110 var ms = (MethodSpec) best;
111 TypeSpec source_type = ms.Parameters.ExtensionMethodType;
112 if (source_type != null) {
113 Argument a = arguments[0];
115 if (TypeManager.IsGenericType (source_type) && InflatedTypeSpec.ContainsTypeParameter (source_type)) {
116 TypeInferenceContext tic = new TypeInferenceContext (source_type.TypeArguments);
117 tic.OutputTypeInference (rc, a.Expr, source_type);
118 if (tic.FixAllTypes (rc)) {
119 source_type = source_type.GetDefinition ().MakeGenericType (tic.InferredTypeArguments);
123 if (!Convert.ImplicitConversionExists (rc, a.Expr, source_type)) {
124 rc.Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
125 best.Name, TypeManager.CSharpName (a.Type));
130 if (best.Name == "SelectMany") {
131 rc.Report.Error (1943, loc,
132 "An expression type is incorrect in a subsequent `from' clause in a query expression with source type `{0}'",
133 arguments[0].GetSignatureForError ());
135 rc.Report.Error (1942, loc,
136 "An expression type in `{0}' clause is incorrect. Type inference failed in the call to `{1}'",
137 best.Name.ToLowerInvariant (), best.Name);
146 public AQueryClause next;
147 public QueryBlock block;
149 protected AQueryClause (QueryBlock block, Expression expr, Location loc)
156 protected override void CloneTo (CloneContext clonectx, Expression target)
158 base.CloneTo (clonectx, target);
160 AQueryClause t = (AQueryClause) target;
163 t.block = (QueryBlock) clonectx.LookupBlock (block);
166 t.next = (AQueryClause) next.Clone (clonectx);
169 protected override Expression DoResolve (ResolveContext ec)
171 return expr.Resolve (ec);
174 public virtual Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parameter)
176 Arguments args = null;
177 CreateArguments (ec, parameter, ref args);
178 lSide = CreateQueryExpression (lSide, args);
180 parameter = CreateChildrenParameters (parameter);
182 Select s = next as Select;
183 if (s == null || s.IsRequired (parameter))
184 return next.BuildQueryClause (ec, lSide, parameter);
186 // Skip transparent select clause if any clause follows
187 if (next.next != null)
188 return next.next.BuildQueryClause (ec, lSide, parameter);
194 protected virtual Parameter CreateChildrenParameters (Parameter parameter)
196 // Have to clone the parameter for any children use, it carries block sensitive data
197 return parameter.Clone ();
200 protected virtual void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
202 args = new Arguments (2);
204 LambdaExpression selector = new LambdaExpression (loc);
206 block.SetParameter (parameter);
207 selector.Block = block;
208 selector.Block.AddStatement (new ContextualReturn (expr));
210 args.Add (new Argument (selector));
213 protected Invocation CreateQueryExpression (Expression lSide, Arguments arguments)
215 return new QueryExpressionInvocation (
216 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
219 protected abstract string MethodName { get; }
221 public AQueryClause Next {
227 public AQueryClause Tail {
229 return next == null ? this : next.Tail;
235 // A query clause with an identifier (range variable)
237 public abstract class ARangeVariableQueryClause : AQueryClause
239 sealed class RangeAnonymousTypeParameter : AnonymousTypeParameter
241 public RangeAnonymousTypeParameter (Expression initializer, RangeVariable parameter)
242 : base (initializer, parameter.Name, parameter.Location)
246 protected override void Error_InvalidInitializer (ResolveContext ec, string initializer)
248 ec.Report.Error (1932, loc, "A range variable `{0}' cannot be initialized with `{1}'",
253 class RangeParameterReference : ParameterReference
257 public RangeParameterReference (Parameter p)
258 : base (null, p.Location)
263 protected override Expression DoResolve (ResolveContext ec)
265 pi = ec.CurrentBlock.ParametersBlock.GetParameterInfo (parameter);
266 return base.DoResolve (ec);
270 protected RangeVariable identifier;
272 protected ARangeVariableQueryClause (QueryBlock block, RangeVariable identifier, Expression expr, Location loc)
273 : base (block, expr, loc)
275 this.identifier = identifier;
278 public FullNamedExpression IdentifierType { get; set; }
280 protected Invocation CreateCastExpression (Expression lSide)
282 return new QueryExpressionInvocation (
283 new QueryExpressionAccess (lSide, "Cast", new TypeArguments (IdentifierType), loc), null);
286 protected override Parameter CreateChildrenParameters (Parameter parameter)
288 return new QueryBlock.TransparentParameter (parameter.Clone (), GetIntoVariable ());
291 protected static Expression CreateRangeVariableType (ResolveContext rc, Parameter parameter, RangeVariable name, Expression init)
293 var args = new List<AnonymousTypeParameter> (2);
296 // The first argument is the reference to the parameter
298 args.Add (new AnonymousTypeParameter (new RangeParameterReference (parameter), parameter.Name, parameter.Location));
301 // The second argument is the linq expression
303 args.Add (new RangeAnonymousTypeParameter (init, name));
306 // Create unique anonymous type
308 return new NewAnonymousType (args, rc.MemberContext.CurrentMemberDefinition.Parent, name.Location);
311 protected virtual RangeVariable GetIntoVariable ()
317 public sealed class RangeVariable : INamedBlockVariable
321 public RangeVariable (string name, Location loc)
338 public bool IsDeclared {
344 public Location Location { get; private set; }
346 public string Name { get; private set; }
350 public Expression CreateReferenceExpression (ResolveContext rc, Location loc)
352 Expression expr = null;
355 // We know the variable name is somewhere in the scope. This generates
356 // an access expression from current block
358 var pb = rc.CurrentBlock.ParametersBlock;
360 if (pb is QueryBlock) {
361 for (int i = pb.Parameters.Count - 1; i >= 0; --i) {
362 var p = pb.Parameters[i];
364 return pb.GetParameterReference (i, loc);
366 var tp = p as QueryBlock.TransparentParameter;
369 expr = pb.GetParameterReference (i, loc);
371 expr = new TransparentMemberAccess (expr, tp.Name);
373 if (tp.Identifier == Name)
374 return new TransparentMemberAccess (expr, Name);
376 if (tp.Parent.Name == Name)
377 return new TransparentMemberAccess (expr, Name);
379 tp = tp.Parent as QueryBlock.TransparentParameter;
389 pb = pb.Parent.ParametersBlock;
394 class QueryStartClause : ARangeVariableQueryClause
396 public QueryStartClause (QueryBlock block, Expression expr, RangeVariable identifier, Location loc)
397 : base (block, identifier, expr, loc)
399 block.AddRangeVariable (identifier);
402 public override Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parameter)
405 expr = expr.Resolve (ec);
409 if (expr.Type == InternalType.Dynamic || expr.Type == TypeManager.void_type) {
410 ec.Report.Error (1979, expr.Location,
411 "Query expression with a source or join sequence of type `{0}' is not allowed",
412 TypeManager.CSharpName (expr.Type));
417 if (IdentifierType != null)
418 expr = CreateCastExpression (expr);
420 if (parameter == null)
423 return next.BuildQueryClause (ec, lSide, new ImplicitLambdaParameter (identifier.Name, identifier.Location));
426 protected override Expression DoResolve (ResolveContext ec)
428 Expression e = BuildQueryClause (ec, null, null);
429 return e.Resolve (ec);
432 protected override string MethodName {
433 get { throw new NotSupportedException (); }
437 public class GroupBy : AQueryClause
439 Expression element_selector;
440 QueryBlock element_block;
442 public GroupBy (QueryBlock block, Expression elementSelector, QueryBlock elementBlock, Expression keySelector, Location loc)
443 : base (block, keySelector, loc)
446 // Optimizes clauses like `group A by A'
448 if (!elementSelector.Equals (keySelector)) {
449 this.element_selector = elementSelector;
450 this.element_block = elementBlock;
454 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
456 base.CreateArguments (ec, parameter, ref args);
458 if (element_selector != null) {
459 LambdaExpression lambda = new LambdaExpression (element_selector.Location);
461 element_block.SetParameter (parameter.Clone ());
462 lambda.Block = element_block;
463 lambda.Block.AddStatement (new ContextualReturn (element_selector));
464 args.Add (new Argument (lambda));
468 protected override void CloneTo (CloneContext clonectx, Expression target)
470 GroupBy t = (GroupBy) target;
471 if (element_selector != null) {
472 t.element_selector = element_selector.Clone (clonectx);
473 t.element_block = (QueryBlock) element_block.Clone (clonectx);
476 base.CloneTo (clonectx, t);
479 protected override string MethodName {
480 get { return "GroupBy"; }
484 public class Join : SelectMany
486 QueryBlock inner_selector, outer_selector;
488 public Join (QueryBlock block, RangeVariable lt, Expression inner, QueryBlock outerSelector, QueryBlock innerSelector, Location loc)
489 : base (block, lt, inner, loc)
491 this.outer_selector = outerSelector;
492 this.inner_selector = innerSelector;
495 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
497 args = new Arguments (4);
499 if (IdentifierType != null)
500 expr = CreateCastExpression (expr);
502 args.Add (new Argument (expr));
504 outer_selector.SetParameter (parameter.Clone ());
505 var lambda = new LambdaExpression (outer_selector.StartLocation);
506 lambda.Block = outer_selector;
507 args.Add (new Argument (lambda));
509 inner_selector.SetParameter (new ImplicitLambdaParameter (identifier.Name, identifier.Location));
510 lambda = new LambdaExpression (inner_selector.StartLocation);
511 lambda.Block = inner_selector;
512 args.Add (new Argument (lambda));
514 base.CreateArguments (ec, parameter, ref args);
517 protected override void CloneTo (CloneContext clonectx, Expression target)
519 Join t = (Join) target;
520 t.inner_selector = (QueryBlock) inner_selector.Clone (clonectx);
521 t.outer_selector = (QueryBlock) outer_selector.Clone (clonectx);
522 base.CloneTo (clonectx, t);
525 protected override string MethodName {
526 get { return "Join"; }
530 public class GroupJoin : Join
532 readonly RangeVariable into;
534 public GroupJoin (QueryBlock block, RangeVariable lt, Expression inner,
535 QueryBlock outerSelector, QueryBlock innerSelector, RangeVariable into, Location loc)
536 : base (block, lt, inner, outerSelector, innerSelector, loc)
541 protected override RangeVariable GetIntoVariable ()
546 protected override string MethodName {
547 get { return "GroupJoin"; }
551 public class Let : ARangeVariableQueryClause
553 public Let (QueryBlock block, RangeVariable identifier, Expression expr, Location loc)
554 : base (block, identifier, expr, loc)
558 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
560 expr = CreateRangeVariableType (ec, parameter, identifier, expr);
561 base.CreateArguments (ec, parameter, ref args);
564 protected override string MethodName {
565 get { return "Select"; }
569 public class Select : AQueryClause
571 public Select (QueryBlock block, Expression expr, Location loc)
572 : base (block, expr, loc)
577 // For queries like `from a orderby a select a'
578 // the projection is transparent and select clause can be safely removed
580 public bool IsRequired (Parameter parameter)
582 SimpleName sn = expr as SimpleName;
586 return sn.Name != parameter.Name;
589 protected override string MethodName {
590 get { return "Select"; }
594 public class SelectMany : ARangeVariableQueryClause
596 public SelectMany (QueryBlock block, RangeVariable identifier, Expression expr, Location loc)
597 : base (block, identifier, expr, loc)
601 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
604 if (IdentifierType != null)
605 expr = CreateCastExpression (expr);
607 base.CreateArguments (ec, parameter.Clone (), ref args);
610 Expression result_selector_expr;
611 QueryBlock result_block;
613 var target = GetIntoVariable ();
614 var target_param = new ImplicitLambdaParameter (target.Name, target.Location);
617 // When select follows use it as a result selector
619 if (next is Select) {
620 result_selector_expr = next.Expr;
622 result_block = next.block;
623 result_block.SetParameters (parameter, target_param);
627 result_selector_expr = CreateRangeVariableType (ec, parameter, target, new SimpleName (target.Name, target.Location));
629 result_block = new QueryBlock (ec.Compiler, block.Parent, block.StartLocation);
630 result_block.SetParameters (parameter, target_param);
633 LambdaExpression result_selector = new LambdaExpression (Location);
634 result_selector.Block = result_block;
635 result_selector.Block.AddStatement (new ContextualReturn (result_selector_expr));
637 args.Add (new Argument (result_selector));
640 protected override string MethodName {
641 get { return "SelectMany"; }
645 public class Where : AQueryClause
647 public Where (QueryBlock block, BooleanExpression expr, Location loc)
648 : base (block, expr, loc)
652 protected override string MethodName {
653 get { return "Where"; }
657 public class OrderByAscending : AQueryClause
659 public OrderByAscending (QueryBlock block, Expression expr)
660 : base (block, expr, expr.Location)
664 protected override string MethodName {
665 get { return "OrderBy"; }
669 public class OrderByDescending : AQueryClause
671 public OrderByDescending (QueryBlock block, Expression expr)
672 : base (block, expr, expr.Location)
676 protected override string MethodName {
677 get { return "OrderByDescending"; }
681 public class ThenByAscending : OrderByAscending
683 public ThenByAscending (QueryBlock block, Expression expr)
688 protected override string MethodName {
689 get { return "ThenBy"; }
693 public class ThenByDescending : OrderByDescending
695 public ThenByDescending (QueryBlock block, Expression expr)
700 protected override string MethodName {
701 get { return "ThenByDescending"; }
706 // Implicit query block
708 public class QueryBlock : ParametersBlock
711 // Transparent parameters are used to package up the intermediate results
712 // and pass them onto next clause
714 public sealed class TransparentParameter : ImplicitLambdaParameter
716 public static int Counter;
717 const string ParameterNamePrefix = "<>__TranspIdent";
719 public readonly Parameter Parent;
720 public readonly string Identifier;
722 public TransparentParameter (Parameter parent, RangeVariable identifier)
723 : base (ParameterNamePrefix + Counter++, identifier.Location)
726 Identifier = identifier.Name;
729 public new static void Reset ()
735 public QueryBlock (CompilerContext ctx, Block parent, Location start)
736 : base (parent, ParametersCompiled.EmptyReadOnlyParameters, start)
738 flags |= Flags.CompilerGenerated;
741 public void AddRangeVariable (RangeVariable variable)
743 variable.Block = this;
744 AddLocalName (variable.Name, variable);
747 public override void Error_AlreadyDeclared (string name, INamedBlockVariable variable, string reason)
749 TopBlock.Report.Error (1931, variable.Location,
750 "A range variable `{0}' conflicts with a previous declaration of `{0}'",
754 public override void Error_AlreadyDeclared (string name, INamedBlockVariable variable)
756 TopBlock.Report.Error (1930, variable.Location,
757 "A range variable `{0}' has already been declared in this scope",
761 public override void Error_AlreadyDeclaredTypeParameter (string name, Location loc)
763 TopBlock.Report.Error (1948, loc,
764 "A range variable `{0}' conflicts with a method type parameter",
768 public void SetParameter (Parameter parameter)
770 base.parameters = new ParametersCompiled (parameter);
771 base.parameter_info = new ParameterInfo[] {
772 new ParameterInfo (this, 0)
776 public void SetParameters (Parameter first, Parameter second)
778 base.parameters = new ParametersCompiled (first, second);
779 base.parameter_info = new ParameterInfo[] {
780 new ParameterInfo (this, 0),
781 new ParameterInfo (this, 1)
786 sealed class TransparentMemberAccess : MemberAccess
788 public TransparentMemberAccess (Expression expr, string name)
793 public override Expression DoResolveLValue (ResolveContext rc, Expression right_side)
795 rc.Report.Error (1947, loc,
796 "A range variable `{0}' cannot be assigned to. Consider using `let' clause to store the value",