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
25 LocatedToken variable;
27 public QueryExpression (LocatedToken variable, AQueryClause query)
28 : base (null, query.Location)
30 this.variable = variable;
34 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
36 Parameter p = CreateBlockParameter (variable);
37 return next.BuildQueryClause (ec, lSide, p, ti);
40 public override Expression DoResolve (EmitContext ec)
42 Expression e = BuildQueryClause (ec, null, null, null);
47 protected override string MethodName {
48 get { throw new NotSupportedException (); }
52 public abstract class AQueryClause : Expression
54 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 Expression Error_MemberLookupFailed (Type container_type, Type qualifier_type,
67 Type queried_type, string name, string class_name, MemberTypes mt, BindingFlags bf)
69 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?",
76 class QueryExpressionInvocation : Invocation
78 public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
79 : base (expr, arguments)
83 protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
85 int errors = Report.Errors;
86 MethodGroupExpr rmg = mg.OverloadResolve (ec, Arguments, true, loc);
87 if (rmg == null && errors == Report.Errors) {
88 // TODO: investigate whether would be better to re-use extension methods error handling
89 Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
90 mg.Name, TypeManager.CSharpName (mg.Type));
97 public AQueryClause next;
98 /*protected*/ public Expression expr;
100 protected AQueryClause (Expression expr, Location loc)
106 protected override void CloneTo (CloneContext clonectx, Expression target)
108 AQueryClause t = (AQueryClause) target;
110 t.expr = expr.Clone (clonectx);
113 t.next = (AQueryClause)next.Clone (clonectx);
116 public override Expression DoResolve (EmitContext ec)
118 return expr.DoResolve (ec);
121 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
123 ArrayList args = new ArrayList (1);
124 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
125 lSide = CreateQueryExpression (lSide, args);
127 Select s = next as Select;
128 if (s == null || s.IsRequired (parameter))
129 return next.BuildQueryClause (ec, lSide, parameter, ti);
131 // Skip transparent select clause if any clause follows
132 if (next.next != null)
133 return next.next.BuildQueryClause (ec, lSide, parameter, ti);
139 protected static Parameter CreateBlockParameter (LocatedToken li)
141 return new ImplicitQueryParameter (li);
144 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
146 return new QueryExpressionInvocation (
147 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
150 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
152 return new QueryExpressionInvocation (
153 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
156 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter parameter, TransparentIdentifiersScope ti)
158 return CreateSelectorArgument (ec, expr, new Parameter [] { parameter }, ti);
161 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter[] parameters, TransparentIdentifiersScope ti)
163 Parameters p = new Parameters (parameters);
165 LambdaExpression selector = new LambdaExpression (
166 null, null, (TypeContainer)ec.TypeContainer, p, ec.CurrentBlock, loc);
167 selector.Block = new SelectorBlock (ec.CurrentBlock, p, ti, loc);
168 selector.Block.AddStatement (new Return (expr, loc));
170 if (!ec.IsInProbingMode) {
171 selector.CreateAnonymousHelpers ();
173 // TODO: I am not sure where this should be done to work
174 // correctly with anonymous containerss and was called only once
175 // FIXME: selector.RootScope == null for nested anonymous
177 if (selector.RootScope != null)
178 selector.RootScope.DefineType ();
181 return new Argument (selector);
184 public override void Emit (EmitContext ec)
186 throw new NotSupportedException ();
189 protected abstract string MethodName { get; }
191 public virtual AQueryClause Next {
197 public AQueryClause Tail {
199 return next == null ? this : next.Tail;
205 // A query clause with an identifier (range variable)
207 public abstract class ARangeVariableQueryClause : AQueryClause
209 LocatedToken variable;
210 protected Expression element_selector;
212 protected ARangeVariableQueryClause (LocatedToken variable, Expression expr, Location loc)
215 this.variable = variable;
218 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
219 ref Parameter parameter, TransparentIdentifiersScope ti)
221 args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
222 args.Add (CreateSelectorArgument (ec, element_selector,
223 new Parameter [] { parentParameter, parameter }, ti));
227 // Customization for range variables which not only creates a lambda expression but
228 // also builds a chain of range varible pairs
230 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
232 Parameter parameter = CreateBlockParameter (variable);
236 // Builds transparent identifiers, each identifier includes its parent
237 // type at index 0, and new value at index 1. This is not valid for the
238 // first one which includes two values directly.
240 ArrayList transp_args = new ArrayList (2);
241 transp_args.Add (new AnonymousTypeParameter (parentParameter));
242 transp_args.Add (CreateAnonymousTypeVariable (parameter));
243 element_selector = new AnonymousTypeDeclaration (transp_args, (TypeContainer) ec.TypeContainer, loc);
246 ArrayList args = new ArrayList ();
247 AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
249 lSide = CreateQueryExpression (lSide, args);
252 // Parameter identifiers go to the scope
254 string[] identifiers;
256 identifiers = new string [] { parentParameter.Name, parameter.Name };
258 identifiers = new string [] { parameter.Name };
261 TransparentParameter tp = new TransparentParameter (loc);
262 return next.BuildQueryClause (ec, lSide, tp,
263 new TransparentIdentifiersScope (ti, tp, identifiers));
269 protected override void CloneTo (CloneContext clonectx, Expression target)
271 ARangeVariableQueryClause t = (ARangeVariableQueryClause) target;
272 if (element_selector != null)
273 t.element_selector = element_selector.Clone (clonectx);
274 base.CloneTo (clonectx, t);
278 // For transparent identifiers, creates an instance of variable expression
280 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
282 return new AnonymousTypeParameter (parameter);
286 public class QueryStartClause : AQueryClause
288 public QueryStartClause (Expression expr)
289 : base (expr, expr.Location)
293 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
295 return next.BuildQueryClause (ec, expr, parameter, ti);
298 public override Expression DoResolve (EmitContext ec)
300 Expression e = BuildQueryClause (ec, null, null, null);
301 return e.Resolve (ec);
304 protected override string MethodName {
305 get { throw new NotSupportedException (); }
309 public class Cast : QueryStartClause
311 // We don't have to clone cast type
312 readonly Expression type_expr;
314 public Cast (Expression type, Expression expr)
317 this.type_expr = type;
320 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
322 lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
324 return next.BuildQueryClause (ec, lSide, parameter, ti);
329 protected override string MethodName {
330 get { return "Cast"; }
334 public class GroupBy : AQueryClause
336 Expression element_selector;
338 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
339 : base (keySelector, loc)
341 this.element_selector = elementSelector;
344 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
346 ArrayList args = new ArrayList (2);
347 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
349 // A query can be optimized when selector is not group by specific
350 if (!element_selector.Equals (lSide))
351 args.Add (CreateSelectorArgument (ec, element_selector, parameter, ti));
353 lSide = CreateQueryExpression (lSide, args);
355 return next.BuildQueryClause (ec, lSide, parameter, ti);
360 protected override void CloneTo (CloneContext clonectx, Expression target)
362 GroupBy t = (GroupBy) target;
363 t.element_selector = element_selector.Clone (clonectx);
364 base.CloneTo (clonectx, t);
367 protected override string MethodName {
368 get { return "GroupBy"; }
372 public class Join : ARangeVariableQueryClause
374 Expression projection;
375 Expression inner_selector, outer_selector;
377 public Join (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
378 : base (variable, inner, loc)
380 this.outer_selector = outerSelector;
381 this.inner_selector = innerSelector;
384 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
385 ref Parameter parameter, TransparentIdentifiersScope ti)
387 args.Add (new Argument (expr));
388 args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
389 args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
391 parameter = CreateResultSelectorParameter (parameter);
392 if (projection == null) {
393 ArrayList join_args = new ArrayList (2);
394 join_args.Add (new AnonymousTypeParameter (parentParameter));
395 join_args.Add (new AnonymousTypeParameter (parameter));
396 projection = new AnonymousTypeDeclaration (join_args, (TypeContainer) ec.TypeContainer, loc);
399 args.Add (CreateSelectorArgument (ec, projection,
400 new Parameter [] { parentParameter, parameter }, ti));
403 protected override void CloneTo (CloneContext clonectx, Expression target)
405 Join t = (Join) target;
406 t.projection = projection.Clone (clonectx);
407 t.inner_selector = inner_selector.Clone (clonectx);
408 t.outer_selector = outer_selector.Clone (clonectx);
409 base.CloneTo (clonectx, t);
412 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
417 public override AQueryClause Next {
419 // Use select as join projection
420 if (value is Select) {
421 projection = value.expr;
430 protected override string MethodName {
431 get { return "Join"; }
435 public class GroupJoin : Join
437 readonly LocatedToken into_variable;
439 public GroupJoin (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector,
440 LocatedToken into, Location loc)
441 : base (variable, inner, outerSelector, innerSelector, loc)
443 this.into_variable = into;
446 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
449 // into variable is used as result selector and it's passed as
450 // transparent identifiers to the next clause
452 return CreateBlockParameter (into_variable);
455 protected override string MethodName {
456 get { return "GroupJoin"; }
460 public class Let : ARangeVariableQueryClause
462 class RangeAnonymousTypeParameter : AnonymousTypeParameter
464 readonly Parameter parameter;
466 public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
467 : base (initializer, parameter.Name, parameter.Location)
469 this.parameter = parameter;
472 public override Expression DoResolve (EmitContext ec)
474 Expression e = base.DoResolve (ec);
477 // Spread resolved initializer type
479 parameter.ParameterType = type;
480 parameter.Resolve (ec);
487 public Let (LocatedToken variable, Expression expr, Location loc)
488 : base (variable, expr, loc)
492 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
493 ref Parameter parameter, TransparentIdentifiersScope ti)
495 args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
498 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
500 return new RangeAnonymousTypeParameter (expr, parameter);
503 protected override string MethodName {
504 get { return "Select"; }
508 public class Select : AQueryClause
510 public Select (Expression expr, Location loc)
516 // For queries like `from a orderby a select a'
517 // the projection is transparent and select clause can be safely removed
519 public bool IsRequired (Parameter parameter)
521 SimpleName sn = expr as SimpleName;
525 return sn.Name != parameter.Name;
528 protected override string MethodName {
529 get { return "Select"; }
533 public class SelectMany : ARangeVariableQueryClause
535 public SelectMany (LocatedToken variable, Expression expr)
536 : base (variable, expr, expr.Location)
540 protected override string MethodName {
541 get { return "SelectMany"; }
544 public override AQueryClause Next {
546 element_selector = value.expr;
548 // Can be optimized as SelectMany element selector
557 public class Where : AQueryClause
559 public Where (Expression expr, Location loc)
564 protected override string MethodName {
565 get { return "Where"; }
569 public class OrderByAscending : AQueryClause
571 public OrderByAscending (Expression expr)
572 : base (expr, expr.Location)
576 protected override string MethodName {
577 get { return "OrderBy"; }
581 public class OrderByDescending : AQueryClause
583 public OrderByDescending (Expression expr)
584 : base (expr, expr.Location)
588 protected override string MethodName {
589 get { return "OrderByDescending"; }
593 public class ThenByAscending : OrderByAscending
595 public ThenByAscending (Expression expr)
600 protected override string MethodName {
601 get { return "ThenBy"; }
605 public class ThenByDescending : OrderByDescending
607 public ThenByDescending (Expression expr)
612 protected override string MethodName {
613 get { return "ThenByDescending"; }
617 class ImplicitQueryParameter : ImplicitLambdaParameter
619 public sealed class ImplicitType : Expression
621 public static ImplicitType Instance = new ImplicitType ();
623 private ImplicitType ()
627 protected override void CloneTo (CloneContext clonectx, Expression target)
632 public override Expression DoResolve (EmitContext ec)
634 throw new NotSupportedException ();
637 public override void Emit (EmitContext ec)
639 throw new NotSupportedException ();
643 public ImplicitQueryParameter (LocatedToken variable)
644 : base (variable.Value, variable.Location)
650 // Transparent parameters are used to package up the intermediate results
651 // and pass them onto next clause
653 public class TransparentParameter : ImplicitLambdaParameter
656 const string ParameterNamePrefix = "<>__TranspIdent";
658 public TransparentParameter (Location loc)
659 : base (ParameterNamePrefix + counter++, loc)
665 // Transparent identifiers are stored in nested anonymous types, each type can contain
666 // up to 2 identifiers or 1 identifier and parent type.
668 public class TransparentIdentifiersScope
670 readonly string [] identifiers;
671 readonly TransparentIdentifiersScope parent;
672 readonly TransparentParameter parameter;
674 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
675 TransparentParameter parameter, string [] identifiers)
677 this.parent = parent;
678 this.parameter = parameter;
679 this.identifiers = identifiers;
682 public MemberAccess GetIdentifier (string name)
684 TransparentIdentifiersScope ident = FindIdentifier (name);
688 return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
691 TransparentIdentifiersScope FindIdentifier (string name)
693 foreach (string s in identifiers) {
701 return parent.FindIdentifier (name);
704 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
706 Expression expr = new SimpleName (parameter.Name, parameter.Location);
707 TransparentIdentifiersScope current = this;
708 while (current != end)
710 current = current.parent;
711 expr = new MemberAccess (expr, current.parameter.Name);
719 // Lambda expression block which contains transparent identifiers
721 class SelectorBlock : ToplevelBlock
723 readonly TransparentIdentifiersScope transparent_identifiers;
725 public SelectorBlock (Block block, Parameters parameters,
726 TransparentIdentifiersScope transparentIdentifiers, Location loc)
727 : base (block, parameters, loc)
729 this.transparent_identifiers = transparentIdentifiers;
732 public override Expression GetTransparentIdentifier (string name)
734 Expression expr = null;
735 if (transparent_identifiers != null)
736 expr = transparent_identifiers.GetIdentifier (name);
738 if (expr != null || Container == null)
741 return Container.GetTransparentIdentifier (name);
746 // This block is actually never used, it is used by parser only
748 public class QueryBlock : Block
750 Hashtable range_variables = new Hashtable ();
752 public QueryBlock (Block parent, Location start)
753 : base (parent, start, Location.Null)
757 protected override void AddVariable (LocalInfo li)
759 string name = li.Name;
760 if (range_variables.Contains (name)) {
761 Location conflict = (Location)range_variables [name];
762 Report.SymbolRelatedToPreviousError (conflict, name);
763 Error_AlreadyDeclared (li.Location, name);
767 range_variables.Add (name, li.Location);
770 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
772 Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
776 protected override void Error_AlreadyDeclared (Location loc, string var)
778 Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
782 protected override void Error_AlreadyDeclaredTypeParameter (Location loc, string name)
784 Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",