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.Reflection;
13 using System.Collections.Generic;
15 namespace Mono.CSharp.Linq
17 public class QueryExpression : AQueryClause
19 public QueryExpression (AQueryClause start)
20 : base (null, null, Location.Null)
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 #region IErrorHandler Members
90 bool OverloadResolver.IErrorHandler.AmbiguousCandidates (ResolveContext ec, MemberSpec best, MemberSpec ambiguous)
92 ec.Report.SymbolRelatedToPreviousError (best);
93 ec.Report.SymbolRelatedToPreviousError (ambiguous);
94 ec.Report.Error (1940, loc, "Ambiguous implementation of the query pattern `{0}' for source type `{1}'",
95 best.Name, mg.InstanceExpression.GetSignatureForError ());
99 bool OverloadResolver.IErrorHandler.ArgumentMismatch (ResolveContext rc, MemberSpec best, Argument arg, int index)
104 bool OverloadResolver.IErrorHandler.NoArgumentMatch (ResolveContext rc, MemberSpec best)
109 bool OverloadResolver.IErrorHandler.TypeInferenceFailed (ResolveContext rc, MemberSpec best)
111 var ms = (MethodSpec) best;
112 TypeSpec source_type = ms.Parameters.ExtensionMethodType;
113 if (source_type != null) {
114 Argument a = arguments[0];
116 if (TypeManager.IsGenericType (source_type) && TypeManager.ContainsGenericParameters (source_type)) {
117 TypeInferenceContext tic = new TypeInferenceContext (source_type.TypeArguments);
118 tic.OutputTypeInference (rc, a.Expr, source_type);
119 if (tic.FixAllTypes (rc)) {
120 source_type = source_type.GetDefinition ().MakeGenericType (tic.InferredTypeArguments);
124 if (!Convert.ImplicitConversionExists (rc, a.Expr, source_type)) {
125 rc.Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
126 best.Name, TypeManager.CSharpName (a.Type));
131 if (best.Name == "SelectMany") {
132 rc.Report.Error (1943, loc,
133 "An expression type is incorrect in a subsequent `from' clause in a query expression with source type `{0}'",
134 arguments[0].GetSignatureForError ());
136 rc.Report.Error (1942, loc,
137 "An expression type in `{0}' clause is incorrect. Type inference failed in the call to `{1}'",
138 best.Name.ToLowerInvariant (), best.Name);
147 public AQueryClause next;
148 public QueryBlock block;
150 protected AQueryClause (QueryBlock block, Expression expr, Location loc)
157 protected override void CloneTo (CloneContext clonectx, Expression target)
159 base.CloneTo (clonectx, target);
161 AQueryClause t = (AQueryClause) target;
164 t.block = (QueryBlock) clonectx.LookupBlock (block);
167 t.next = (AQueryClause) next.Clone (clonectx);
170 protected override Expression DoResolve (ResolveContext ec)
172 return expr.Resolve (ec);
175 public virtual Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parameter)
177 Arguments args = null;
178 CreateArguments (ec, parameter, ref args);
179 lSide = CreateQueryExpression (lSide, args);
181 parameter = CreateChildrenParameters (parameter);
183 Select s = next as Select;
184 if (s == null || s.IsRequired (parameter))
185 return next.BuildQueryClause (ec, lSide, parameter);
187 // Skip transparent select clause if any clause follows
188 if (next.next != null)
189 return next.next.BuildQueryClause (ec, lSide, parameter);
195 protected virtual Parameter CreateChildrenParameters (Parameter parameter)
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.Clone ());
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, SimpleMemberName parameter)
242 : base (initializer, parameter.Value, 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 protected SimpleMemberName range_variable;
255 protected ARangeVariableQueryClause (QueryBlock block, SimpleMemberName identifier, Expression expr, Location loc)
256 : base (block, expr, loc)
258 range_variable = identifier;
261 public FullNamedExpression IdentifierType { get; set; }
263 protected Invocation CreateCastExpression (Expression lSide)
265 return new QueryExpressionInvocation (
266 new QueryExpressionAccess (lSide, "Cast", new TypeArguments (IdentifierType), loc), null);
269 protected override Parameter CreateChildrenParameters (Parameter parameter)
271 return new QueryBlock.TransparentParameter (parameter, GetIntoVariable ());
274 protected static Expression CreateRangeVariableType (ResolveContext rc, Parameter parameter, SimpleMemberName name, Expression init)
276 var args = new List<AnonymousTypeParameter> (2);
277 args.Add (new AnonymousTypeParameter (parameter));
278 args.Add (new RangeAnonymousTypeParameter (init, name));
279 return new NewAnonymousType (args, rc.MemberContext.CurrentMemberDefinition.Parent, name.Location);
282 protected virtual SimpleMemberName GetIntoVariable ()
284 return range_variable;
288 class QueryStartClause : ARangeVariableQueryClause
290 public QueryStartClause (QueryBlock block, Expression expr, SimpleMemberName identifier, Location loc)
291 : base (block, identifier, expr, loc)
293 block.AddRangeVariable (identifier);
296 public override Expression BuildQueryClause (ResolveContext ec, Expression lSide, Parameter parameter)
299 expr = expr.Resolve (ec);
303 if (expr.Type == InternalType.Dynamic || expr.Type == TypeManager.void_type) {
304 ec.Report.Error (1979, expr.Location,
305 "Query expression with a source or join sequence of type `{0}' is not allowed",
306 TypeManager.CSharpName (expr.Type));
311 if (IdentifierType != null)
312 expr = CreateCastExpression (expr);
314 if (parameter == null)
317 return next.BuildQueryClause (ec, lSide, new ImplicitLambdaParameter (range_variable.Value, range_variable.Location));
320 protected override Expression DoResolve (ResolveContext ec)
322 Expression e = BuildQueryClause (ec, null, null);
323 return e.Resolve (ec);
326 protected override string MethodName {
327 get { throw new NotSupportedException (); }
331 public class GroupBy : AQueryClause
333 Expression element_selector;
334 QueryBlock element_block;
336 public GroupBy (QueryBlock block, Expression elementSelector, QueryBlock elementBlock, Expression keySelector, Location loc)
337 : base (block, keySelector, loc)
340 // Optimizes clauses like `group A by A'
342 if (!elementSelector.Equals (keySelector)) {
343 this.element_selector = elementSelector;
344 this.element_block = elementBlock;
348 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
350 base.CreateArguments (ec, parameter, ref args);
352 if (element_selector != null) {
353 LambdaExpression lambda = new LambdaExpression (element_selector.Location);
355 element_block.SetParameter (parameter.Clone ());
356 lambda.Block = element_block;
357 lambda.Block.AddStatement (new ContextualReturn (element_selector));
358 args.Add (new Argument (lambda));
362 protected override void CloneTo (CloneContext clonectx, Expression target)
364 GroupBy t = (GroupBy) target;
365 if (element_selector != null) {
366 t.element_selector = element_selector.Clone (clonectx);
367 t.element_block = (QueryBlock) element_block.Clone (clonectx);
370 base.CloneTo (clonectx, t);
373 protected override string MethodName {
374 get { return "GroupBy"; }
378 public class Join : SelectMany
380 QueryBlock inner_selector, outer_selector;
382 public Join (QueryBlock block, SimpleMemberName lt, Expression inner, QueryBlock outerSelector, QueryBlock innerSelector, Location loc)
383 : base (block, lt, inner, loc)
385 this.outer_selector = outerSelector;
386 this.inner_selector = innerSelector;
389 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
391 args = new Arguments (4);
393 if (IdentifierType != null)
394 expr = CreateCastExpression (expr);
396 args.Add (new Argument (expr));
398 outer_selector.SetParameter (parameter.Clone ());
399 var lambda = new LambdaExpression (outer_selector.StartLocation);
400 lambda.Block = outer_selector;
401 args.Add (new Argument (lambda));
403 inner_selector.SetParameter (new ImplicitLambdaParameter (range_variable.Value, range_variable.Location));
404 lambda = new LambdaExpression (inner_selector.StartLocation);
405 lambda.Block = inner_selector;
406 args.Add (new Argument (lambda));
408 base.CreateArguments (ec, parameter, ref args);
411 protected override void CloneTo (CloneContext clonectx, Expression target)
413 Join t = (Join) target;
414 t.inner_selector = (QueryBlock) inner_selector.Clone (clonectx);
415 t.outer_selector = (QueryBlock) outer_selector.Clone (clonectx);
416 base.CloneTo (clonectx, t);
419 protected override string MethodName {
420 get { return "Join"; }
424 public class GroupJoin : Join
426 readonly SimpleMemberName into;
428 public GroupJoin (QueryBlock block, SimpleMemberName lt, Expression inner,
429 QueryBlock outerSelector, QueryBlock innerSelector, SimpleMemberName into, Location loc)
430 : base (block, lt, inner, outerSelector, innerSelector, loc)
435 protected override SimpleMemberName GetIntoVariable ()
440 protected override string MethodName {
441 get { return "GroupJoin"; }
445 public class Let : ARangeVariableQueryClause
447 public Let (QueryBlock block, SimpleMemberName identifier, Expression expr, Location loc)
448 : base (block, identifier, expr, loc)
452 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
454 expr = CreateRangeVariableType (ec, parameter, range_variable, expr);
455 base.CreateArguments (ec, parameter, ref args);
458 protected override string MethodName {
459 get { return "Select"; }
463 public class Select : AQueryClause
465 public Select (QueryBlock block, Expression expr, Location loc)
466 : base (block, expr, loc)
471 // For queries like `from a orderby a select a'
472 // the projection is transparent and select clause can be safely removed
474 public bool IsRequired (Parameter parameter)
476 SimpleName sn = expr as SimpleName;
480 return sn.Name != parameter.Name;
483 protected override string MethodName {
484 get { return "Select"; }
488 public class SelectMany : ARangeVariableQueryClause
490 public SelectMany (QueryBlock block, SimpleMemberName identifier, Expression expr, Location loc)
491 : base (block, identifier, expr, loc)
495 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
498 if (IdentifierType != null)
499 expr = CreateCastExpression (expr);
501 base.CreateArguments (ec, parameter, ref args);
504 Expression result_selector_expr;
505 QueryBlock result_block;
507 var target = GetIntoVariable ();
508 var target_param = new ImplicitLambdaParameter (target.Value, target.Location);
511 // When select follows use it as a result selector
513 if (next is Select) {
514 result_selector_expr = next.Expr;
516 result_block = next.block;
517 result_block.SetParameters (parameter, target_param);
521 result_selector_expr = CreateRangeVariableType (ec, parameter, target, new SimpleName (target.Value, target.Location));
523 result_block = new QueryBlock (ec.Compiler, block.Parent, block.StartLocation);
524 result_block.SetParameters (parameter, target_param);
527 LambdaExpression result_selector = new LambdaExpression (Location);
528 result_selector.Block = result_block;
529 result_selector.Block.AddStatement (new ContextualReturn (result_selector_expr));
531 args.Add (new Argument (result_selector));
534 protected override string MethodName {
535 get { return "SelectMany"; }
539 public class Where : AQueryClause
541 public Where (QueryBlock block, BooleanExpression expr, Location loc)
542 : base (block, expr, loc)
546 protected override string MethodName {
547 get { return "Where"; }
550 protected override void CreateArguments (ResolveContext ec, Parameter parameter, ref Arguments args)
552 base.CreateArguments (ec, parameter, ref args);
556 public class OrderByAscending : AQueryClause
558 public OrderByAscending (QueryBlock block, Expression expr)
559 : base (block, expr, expr.Location)
563 protected override string MethodName {
564 get { return "OrderBy"; }
568 public class OrderByDescending : AQueryClause
570 public OrderByDescending (QueryBlock block, Expression expr)
571 : base (block, expr, expr.Location)
575 protected override string MethodName {
576 get { return "OrderByDescending"; }
580 public class ThenByAscending : OrderByAscending
582 public ThenByAscending (QueryBlock block, Expression expr)
587 protected override string MethodName {
588 get { return "ThenBy"; }
592 public class ThenByDescending : OrderByDescending
594 public ThenByDescending (QueryBlock block, Expression expr)
599 protected override string MethodName {
600 get { return "ThenByDescending"; }
605 // Implicit query block
607 public class QueryBlock : ToplevelBlock
610 // Transparent parameters are used to package up the intermediate results
611 // and pass them onto next clause
613 public sealed class TransparentParameter : ImplicitLambdaParameter
615 public static int Counter;
616 const string ParameterNamePrefix = "<>__TranspIdent";
618 public readonly Parameter Parent;
619 public readonly string Identifier;
621 public TransparentParameter (Parameter parent, SimpleMemberName identifier)
622 : base (ParameterNamePrefix + Counter++, identifier.Location)
625 Identifier = identifier.Value;
628 public new static void Reset ()
634 sealed class RangeVariable : IKnownVariable
636 public RangeVariable (QueryBlock block, Location loc)
642 public Block Block { get; private set; }
644 public Location Location { get; private set; }
647 List<SimpleMemberName> range_variables;
649 public QueryBlock (CompilerContext ctx, Block parent, Location start)
650 : base (ctx, parent, ParametersCompiled.EmptyReadOnlyParameters, start)
654 public void AddRangeVariable (SimpleMemberName name)
656 if (!CheckParentConflictName (this, name.Value, name.Location))
659 if (range_variables == null)
660 range_variables = new List<SimpleMemberName> ();
662 range_variables.Add (name);
663 AddKnownVariable (name.Value, new RangeVariable (this, name.Location));
667 // Query parameter reference can include transparent parameters
669 protected override Expression GetParameterReferenceExpression (string name, Location loc)
671 Expression expr = base.GetParameterReferenceExpression (name, loc);
675 TransparentParameter tp = parameters [0] as TransparentParameter;
677 if (tp.Identifier == name)
680 TransparentParameter tp_next = tp.Parent as TransparentParameter;
681 if (tp_next == null && tp.Parent.Name == name)
688 expr = new SimpleName (parameters[0].Name, loc);
689 TransparentParameter tp_cursor = (TransparentParameter) parameters[0];
690 while (tp_cursor != tp) {
691 tp_cursor = (TransparentParameter) tp_cursor.Parent;
692 expr = new TransparentMemberAccess (expr, tp_cursor.Name);
695 return new TransparentMemberAccess (expr, name);
701 protected override bool HasParameterWithName (string name)
703 return range_variables != null && range_variables.Exists (l => l.Value == name);
706 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
708 Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
712 protected override void Error_AlreadyDeclared (Location loc, string var)
714 Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
718 public override void Error_AlreadyDeclaredTypeParameter (Location loc, string name, string conflict)
720 Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",
724 public void SetParameter (Parameter parameter)
726 base.parameters = new ParametersCompiled (null, parameter);
727 base.parameter_info = new ToplevelParameterInfo [] {
728 new ToplevelParameterInfo (this, 0)
732 public void SetParameters (Parameter first, Parameter second)
734 base.parameters = new ParametersCompiled (null, first, second);
735 base.parameter_info = new ToplevelParameterInfo[] {
736 new ToplevelParameterInfo (this, 0),
737 new ToplevelParameterInfo (this, 1)
742 sealed class TransparentMemberAccess : MemberAccess
744 public TransparentMemberAccess (Expression expr, string name)