2 // linq.cs: support for query expressions
4 // Authors: Marek Safar (marek.safar@gmail.com)
6 // Licensed under the terms of the GNU GPL
8 // (C) 2007 Novell, Inc
12 using System.Reflection;
13 using System.Collections;
15 namespace Mono.CSharp.Linq
18 // Expression should be IExpression to save some memory and make a few things
23 public class QueryExpression : AQueryClause
27 public QueryExpression (Block block, AQueryClause query)
28 : base (null, query.Location)
34 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
36 Parameter p = CreateBlockParameter (block);
37 return next.BuildQueryClause (ec, lSide, p, ti);
40 protected override void CloneTo (CloneContext clonectx, Expression target)
42 QueryExpression t = (QueryExpression) target;
43 t.block = (Block)block.Clone (clonectx);
44 base.CloneTo (clonectx, t);
47 public override Expression DoResolve (EmitContext ec)
49 Expression e = BuildQueryClause (ec, null, null, null);
54 protected override string MethodName {
55 get { throw new NotSupportedException (); }
59 public abstract class AQueryClause : Expression
61 class QueryExpressionAccess : MemberAccess
63 public QueryExpressionAccess (Expression expr, string methodName, Location loc)
64 : base (expr, methodName, loc)
68 public QueryExpressionAccess (Expression expr, string methodName, TypeArguments typeArguments, Location loc)
69 : base (expr, methodName, typeArguments, loc)
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)
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?",
83 class QueryExpressionInvocation : Invocation
85 public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
86 : base (expr, arguments)
90 protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
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));
104 public AQueryClause next;
105 /*protected*/ public Expression expr;
107 protected AQueryClause (Expression expr, Location loc)
113 protected override void CloneTo (CloneContext clonectx, Expression target)
115 AQueryClause t = (AQueryClause) target;
117 t.expr = expr.Clone (clonectx);
120 t.next = (AQueryClause)next.Clone (clonectx);
123 public override Expression DoResolve (EmitContext ec)
125 return expr.DoResolve (ec);
128 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
130 ArrayList args = new ArrayList (1);
131 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
132 lSide = CreateQueryExpression (lSide, args);
134 Select s = next as Select;
135 if (s == null || s.IsRequired (parameter))
136 return next.BuildQueryClause (ec, lSide, parameter, ti);
138 // Skip transparent select clause if any clause follows
139 if (next.next != null)
140 return next.next.BuildQueryClause (ec, lSide, parameter, ti);
146 protected static Parameter CreateBlockParameter (Block block)
148 ICollection values = block.Variables.Values;
149 if (values.Count != 1)
150 throw new NotImplementedException ("Count != 1");
152 IEnumerator enumerator = values.GetEnumerator ();
153 enumerator.MoveNext ();
154 LocalInfo li = (LocalInfo) enumerator.Current;
156 return new ImplicitQueryParameter (li);
159 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
161 return new QueryExpressionInvocation (
162 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
165 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
167 return new QueryExpressionInvocation (
168 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
171 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter parameter, TransparentIdentifiersScope ti)
173 return CreateSelectorArgument (ec, expr, new Parameter [] { parameter }, ti);
176 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter[] parameters, TransparentIdentifiersScope ti)
178 Parameters p = new Parameters (parameters);
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));
185 if (!ec.IsInProbingMode) {
186 selector.CreateAnonymousHelpers ();
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
192 if (selector.RootScope != null)
193 selector.RootScope.DefineType ();
196 return new Argument (selector);
199 public override void Emit (EmitContext ec)
201 throw new NotSupportedException ();
204 protected abstract string MethodName { get; }
206 public virtual AQueryClause Next {
212 public AQueryClause Tail {
214 return next == null ? this : next.Tail;
220 // A query clause with an identifier (range variable)
222 public abstract class ARangeVariableQueryClause : AQueryClause
225 protected Expression element_selector;
227 protected ARangeVariableQueryClause (Block block, Expression expr, Location loc)
233 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
234 ref Parameter parameter, TransparentIdentifiersScope ti)
236 args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
237 args.Add (CreateSelectorArgument (ec, element_selector,
238 new Parameter [] { parentParameter, parameter }, ti));
242 // Customization for range variables which not only creates a lambda expression but
243 // also builds a chain of range varible pairs
245 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
247 Parameter parameter = CreateBlockParameter (block);
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.
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);
261 ArrayList args = new ArrayList ();
262 AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
264 lSide = CreateQueryExpression (lSide, args);
267 // Parameter identifiers go to the scope
269 string[] identifiers;
271 identifiers = new string [] { parentParameter.Name, parameter.Name };
273 identifiers = new string [] { parameter.Name };
276 TransparentParameter tp = new TransparentParameter (loc);
277 return next.BuildQueryClause (ec, lSide, tp,
278 new TransparentIdentifiersScope (ti, tp, identifiers));
284 protected override void CloneTo (CloneContext clonectx, Expression target)
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);
293 // For transparent identifiers, creates an instance of variable expression
295 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
297 return new AnonymousTypeParameter (parameter);
301 public class QueryStartClause : AQueryClause
303 public QueryStartClause (Expression expr)
304 : base (expr, expr.Location)
308 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
310 return next.BuildQueryClause (ec, expr, parameter, ti);
313 public override Expression DoResolve (EmitContext ec)
315 Expression e = BuildQueryClause (ec, null, null, null);
316 return e.Resolve (ec);
319 protected override string MethodName {
320 get { throw new NotSupportedException (); }
324 public class Cast : QueryStartClause
326 // We don't have to clone cast type
327 readonly Expression type_expr;
329 public Cast (Expression type, Expression expr)
332 this.type_expr = type;
335 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
337 lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
339 return next.BuildQueryClause (ec, lSide, parameter, ti);
344 protected override string MethodName {
345 get { return "Cast"; }
349 public class GroupBy : AQueryClause
351 Expression element_selector;
353 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
354 : base (keySelector, loc)
356 this.element_selector = elementSelector;
359 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
361 ArrayList args = new ArrayList (2);
362 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
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));
368 lSide = CreateQueryExpression (lSide, args);
370 return next.BuildQueryClause (ec, lSide, parameter, ti);
375 protected override void CloneTo (CloneContext clonectx, Expression target)
377 GroupBy t = (GroupBy) target;
378 t.element_selector = element_selector.Clone (clonectx);
379 base.CloneTo (clonectx, t);
382 protected override string MethodName {
383 get { return "GroupBy"; }
387 public class Join : ARangeVariableQueryClause
389 Expression projection;
390 Expression inner_selector, outer_selector;
392 public Join (Block block, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
393 : base (block, inner, loc)
395 this.outer_selector = outerSelector;
396 this.inner_selector = innerSelector;
399 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
400 ref Parameter parameter, TransparentIdentifiersScope ti)
402 args.Add (new Argument (expr));
403 args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
404 args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
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);
414 args.Add (CreateSelectorArgument (ec, projection,
415 new Parameter [] { parentParameter, parameter }, ti));
418 protected override void CloneTo (CloneContext clonectx, Expression target)
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);
427 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
432 public override AQueryClause Next {
434 // Use select as join projection
435 if (value is Select) {
436 projection = value.expr;
445 protected override string MethodName {
446 get { return "Join"; }
450 public class GroupJoin : Join
452 string into_variable;
454 public GroupJoin (Block block, Expression inner, Expression outerSelector, Expression innerSelector,
455 string into, Location loc)
456 : base (block, inner, outerSelector, innerSelector, loc)
458 this.into_variable = into;
461 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
464 // into variable is used as result selector and it's passed as
465 // transparent identifiers to the next clause
467 return new ImplicitLambdaParameter (into_variable, loc);
470 protected override string MethodName {
471 get { return "GroupJoin"; }
475 public class Let : ARangeVariableQueryClause
477 class RangeAnonymousTypeParameter : AnonymousTypeParameter
479 readonly Parameter parameter;
481 public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
482 : base (initializer, parameter.Name, parameter.Location)
484 this.parameter = parameter;
487 public override Expression DoResolve (EmitContext ec)
489 Expression e = base.DoResolve (ec);
492 // Spread resolved initializer type
494 parameter.ParameterType = type;
495 parameter.Resolve (ec);
502 public Let (Block block, Expression expr, Location loc)
503 : base (block, expr, loc)
507 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
508 ref Parameter parameter, TransparentIdentifiersScope ti)
510 args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
513 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
515 return new RangeAnonymousTypeParameter (expr, parameter);
518 protected override string MethodName {
519 get { return "Select"; }
523 public class Select : AQueryClause
525 public Select (Expression expr, Location loc)
531 // For queries like `from a orderby a select a'
532 // the projection is transparent and select clause can be safely removed
534 public bool IsRequired (Parameter parameter)
536 SimpleName sn = expr as SimpleName;
540 return sn.Name != parameter.Name;
543 protected override string MethodName {
544 get { return "Select"; }
548 public class SelectMany : ARangeVariableQueryClause
550 public SelectMany (Block block, Expression expr)
551 : base (block, expr, expr.Location)
555 protected override string MethodName {
556 get { return "SelectMany"; }
559 public override AQueryClause Next {
561 element_selector = value.expr;
563 // Can be optimized as SelectMany element selector
572 public class Where : AQueryClause
574 public Where (Expression expr, Location loc)
579 protected override string MethodName {
580 get { return "Where"; }
584 public class OrderByAscending : AQueryClause
586 public OrderByAscending (Expression expr)
587 : base (expr, expr.Location)
591 protected override string MethodName {
592 get { return "OrderBy"; }
596 public class OrderByDescending : AQueryClause
598 public OrderByDescending (Expression expr)
599 : base (expr, expr.Location)
603 protected override string MethodName {
604 get { return "OrderByDescending"; }
608 public class ThenByAscending : OrderByAscending
610 public ThenByAscending (Expression expr)
615 protected override string MethodName {
616 get { return "ThenBy"; }
620 public class ThenByDescending : OrderByDescending
622 public ThenByDescending (Expression expr)
627 protected override string MethodName {
628 get { return "ThenByDescending"; }
632 class ImplicitQueryParameter : ImplicitLambdaParameter
634 public sealed class ImplicitType : Expression
636 public static ImplicitType Instance = new ImplicitType ();
638 private ImplicitType ()
642 protected override void CloneTo (CloneContext clonectx, Expression target)
647 public override Expression DoResolve (EmitContext ec)
649 throw new NotSupportedException ();
652 public override void Emit (EmitContext ec)
654 throw new NotSupportedException ();
658 readonly LocalInfo variable;
660 public ImplicitQueryParameter (LocalInfo variable)
661 : base (variable.Name, variable.Location)
663 this.variable = variable;
666 public override bool Resolve (IResolveContext ec)
668 if (!base.Resolve (ec))
671 variable.VariableType = parameter_type;
677 // Transparent parameters are used to package up the intermediate results
678 // and pass them onto next clause
680 public class TransparentParameter : ImplicitLambdaParameter
683 const string ParameterNamePrefix = "<>__TranspIdent";
685 public TransparentParameter (Location loc)
686 : base (ParameterNamePrefix + counter++, loc)
692 // Transparent identifiers are stored in nested anonymous types, each type can contain
693 // up to 2 identifiers or 1 identifier and parent type.
695 public class TransparentIdentifiersScope
697 readonly string [] identifiers;
698 readonly TransparentIdentifiersScope parent;
699 readonly TransparentParameter parameter;
701 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
702 TransparentParameter parameter, string [] identifiers)
704 this.parent = parent;
705 this.parameter = parameter;
706 this.identifiers = identifiers;
709 public MemberAccess GetIdentifier (string name)
711 TransparentIdentifiersScope ident = FindIdentifier (name);
715 return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
718 TransparentIdentifiersScope FindIdentifier (string name)
720 foreach (string s in identifiers) {
728 return parent.FindIdentifier (name);
731 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
733 Expression expr = new SimpleName (parameter.Name, parameter.Location);
734 TransparentIdentifiersScope current = this;
735 while (current != end)
737 current = current.parent;
738 expr = new MemberAccess (expr, current.parameter.Name);
746 // Lambda expression block which contains transparent identifiers
748 class SelectorBlock : ToplevelBlock
750 readonly TransparentIdentifiersScope transparent_identifiers;
752 public SelectorBlock (Block block, Parameters parameters,
753 TransparentIdentifiersScope transparentIdentifiers, Location loc)
754 : base (block, parameters, loc)
756 this.transparent_identifiers = transparentIdentifiers;
759 public override Expression GetTransparentIdentifier (string name)
761 if (transparent_identifiers == null)
764 return transparent_identifiers.GetIdentifier (name);