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;
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, MethodGroupExpr.IErrorHandler
78 public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
79 : base (expr, arguments)
83 protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
85 mg.CustomErrorHandler = this;
86 MethodGroupExpr rmg = mg.OverloadResolve (ec, ref Arguments, false, loc);
90 public bool NoExactMatch (EmitContext ec, MethodBase method)
93 ParameterData pd = TypeManager.GetParameterData (method);
94 Type source_type = pd.ExtensionMethodType;
95 if (source_type != null) {
96 Argument a = (Argument) Arguments [0];
98 if (source_type.IsGenericType && source_type.ContainsGenericParameters) {
99 TypeInferenceContext tic = new TypeInferenceContext (source_type.GetGenericArguments ());
100 tic.OutputTypeInference (ec, a.Expr, source_type);
101 if (tic.FixAllTypes ()) {
102 source_type = source_type.GetGenericTypeDefinition ().MakeGenericType (tic.InferredTypeArguments);
106 if (!Convert.ImplicitConversionExists (ec, a.Expr, source_type)) {
107 Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
108 mg.Name, TypeManager.CSharpName (a.Type));
113 if (!method.IsGenericMethod)
116 Report.Error (1942, loc, "Type inference failed to infer type argument for `{0}' clause. " +
117 "Try specifying the type argument explicitly",
126 public AQueryClause next;
127 /*protected*/ public Expression expr;
129 protected AQueryClause (Expression expr, Location loc)
135 protected override void CloneTo (CloneContext clonectx, Expression target)
137 AQueryClause t = (AQueryClause) target;
139 t.expr = expr.Clone (clonectx);
142 t.next = (AQueryClause)next.Clone (clonectx);
145 public override Expression CreateExpressionTree (EmitContext ec)
147 // Should not be reached
148 throw new NotSupportedException ("ET");
151 public override Expression DoResolve (EmitContext ec)
153 return expr.DoResolve (ec);
156 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
158 ArrayList args = new ArrayList (1);
159 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
160 lSide = CreateQueryExpression (lSide, args);
162 Select s = next as Select;
163 if (s == null || s.IsRequired (parameter))
164 return next.BuildQueryClause (ec, lSide, parameter, ti);
166 // Skip transparent select clause if any clause follows
167 if (next.next != null)
168 return next.next.BuildQueryClause (ec, lSide, parameter, ti);
174 protected static Parameter CreateBlockParameter (LocatedToken li)
176 return new ImplicitQueryParameter (li);
179 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
181 return new QueryExpressionInvocation (
182 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
185 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
187 return new QueryExpressionInvocation (
188 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
191 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter parameter, TransparentIdentifiersScope ti)
193 return CreateSelectorArgument (ec, expr, new Parameter [] { parameter }, ti);
196 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter[] parameters, TransparentIdentifiersScope ti)
198 Parameters p = new Parameters (parameters);
200 LambdaExpression selector = new LambdaExpression ((TypeContainer)ec.TypeContainer, p, loc);
201 selector.Block = new SelectorBlock (ec.CurrentBlock, p, ti, loc);
202 selector.Block.AddStatement (new ContextualReturn (expr));
204 return new Argument (selector);
207 public override void Emit (EmitContext ec)
209 throw new NotSupportedException ();
212 protected abstract string MethodName { get; }
214 public virtual AQueryClause Next {
220 public AQueryClause Tail {
222 return next == null ? this : next.Tail;
228 // A query clause with an identifier (range variable)
230 public abstract class ARangeVariableQueryClause : AQueryClause
232 LocatedToken variable;
233 protected Expression element_selector;
235 protected ARangeVariableQueryClause (LocatedToken variable, Expression expr, Location loc)
238 this.variable = variable;
241 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
242 ref Parameter parameter, TransparentIdentifiersScope ti)
244 args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
245 args.Add (CreateSelectorArgument (ec, element_selector,
246 new Parameter [] { parentParameter, parameter }, ti));
250 // Customization for range variables which not only creates a lambda expression but
251 // also builds a chain of range varible pairs
253 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
255 Parameter parameter = CreateBlockParameter (variable);
259 // Builds transparent identifiers, each identifier includes its parent
260 // type at index 0, and new value at index 1. This is not valid for the
261 // first one which includes two values directly.
263 ArrayList transp_args = new ArrayList (2);
264 transp_args.Add (new AnonymousTypeParameter (parentParameter));
265 transp_args.Add (CreateAnonymousTypeVariable (parameter));
266 element_selector = new AnonymousTypeDeclaration (transp_args, (TypeContainer) ec.TypeContainer, loc);
269 ArrayList args = new ArrayList ();
270 AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
272 lSide = CreateQueryExpression (lSide, args);
275 // Parameter identifiers go to the scope
277 string[] identifiers;
279 identifiers = new string [] { parentParameter.Name, parameter.Name };
281 identifiers = new string [] { parameter.Name };
284 TransparentParameter tp = new TransparentParameter (loc);
285 return next.BuildQueryClause (ec, lSide, tp,
286 new TransparentIdentifiersScope (ti, tp, identifiers));
292 protected override void CloneTo (CloneContext clonectx, Expression target)
294 ARangeVariableQueryClause t = (ARangeVariableQueryClause) target;
295 if (element_selector != null)
296 t.element_selector = element_selector.Clone (clonectx);
297 base.CloneTo (clonectx, t);
301 // For transparent identifiers, creates an instance of variable expression
303 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
305 return new AnonymousTypeParameter (parameter);
309 public class QueryStartClause : AQueryClause
311 public QueryStartClause (Expression expr)
312 : base (expr, expr.Location)
316 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
318 return next.BuildQueryClause (ec, expr, parameter, ti);
321 public override Expression DoResolve (EmitContext ec)
323 Expression e = BuildQueryClause (ec, null, null, null);
324 return e.Resolve (ec);
327 protected override string MethodName {
328 get { throw new NotSupportedException (); }
332 public class Cast : QueryStartClause
334 // We don't have to clone cast type
335 readonly Expression type_expr;
337 public Cast (Expression type, Expression expr)
340 this.type_expr = type;
343 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
345 lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
347 return next.BuildQueryClause (ec, lSide, parameter, ti);
352 protected override string MethodName {
353 get { return "Cast"; }
357 public class GroupBy : AQueryClause
359 Expression element_selector;
361 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
362 : base (keySelector, loc)
364 this.element_selector = elementSelector;
367 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
369 ArrayList args = new ArrayList (2);
370 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
372 // A query can be optimized when selector is not group by specific
373 if (!element_selector.Equals (lSide))
374 args.Add (CreateSelectorArgument (ec, element_selector, parameter, ti));
376 lSide = CreateQueryExpression (lSide, args);
378 return next.BuildQueryClause (ec, lSide, parameter, ti);
383 protected override void CloneTo (CloneContext clonectx, Expression target)
385 GroupBy t = (GroupBy) target;
386 t.element_selector = element_selector.Clone (clonectx);
387 base.CloneTo (clonectx, t);
390 protected override string MethodName {
391 get { return "GroupBy"; }
395 public class Join : ARangeVariableQueryClause
397 Expression projection;
398 Expression inner_selector, outer_selector;
400 public Join (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
401 : base (variable, inner, loc)
403 this.outer_selector = outerSelector;
404 this.inner_selector = innerSelector;
407 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
408 ref Parameter parameter, TransparentIdentifiersScope ti)
410 args.Add (new Argument (expr));
411 args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
412 args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
414 parameter = CreateResultSelectorParameter (parameter);
415 if (projection == null) {
416 ArrayList join_args = new ArrayList (2);
417 join_args.Add (new AnonymousTypeParameter (parentParameter));
418 join_args.Add (new AnonymousTypeParameter (parameter));
419 projection = new AnonymousTypeDeclaration (join_args, (TypeContainer) ec.TypeContainer, loc);
422 args.Add (CreateSelectorArgument (ec, projection,
423 new Parameter [] { parentParameter, parameter }, ti));
426 protected override void CloneTo (CloneContext clonectx, Expression target)
428 Join t = (Join) target;
429 t.projection = projection.Clone (clonectx);
430 t.inner_selector = inner_selector.Clone (clonectx);
431 t.outer_selector = outer_selector.Clone (clonectx);
432 base.CloneTo (clonectx, t);
435 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
440 public override AQueryClause Next {
442 // Use select as join projection
443 if (value is Select) {
444 projection = value.expr;
453 protected override string MethodName {
454 get { return "Join"; }
458 public class GroupJoin : Join
460 readonly LocatedToken into_variable;
462 public GroupJoin (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector,
463 LocatedToken into, Location loc)
464 : base (variable, inner, outerSelector, innerSelector, loc)
466 this.into_variable = into;
469 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
472 // into variable is used as result selector and it's passed as
473 // transparent identifiers to the next clause
475 return CreateBlockParameter (into_variable);
478 protected override string MethodName {
479 get { return "GroupJoin"; }
483 public class Let : ARangeVariableQueryClause
485 class RangeAnonymousTypeParameter : AnonymousTypeParameter
487 readonly Parameter parameter;
489 public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
490 : base (initializer, parameter.Name, parameter.Location)
492 this.parameter = parameter;
495 public override Expression DoResolve (EmitContext ec)
497 Expression e = base.DoResolve (ec);
500 // Spread resolved initializer type
502 parameter.ParameterType = type;
503 parameter.Resolve (ec);
509 protected override void Error_InvalidInitializer (Expression initializer)
511 Report.Error (1932, loc, "A range variable `{0}' cannot be initialized with `{1}'",
512 Name, initializer.GetSignatureForError ());
516 public Let (LocatedToken variable, Expression expr, Location loc)
517 : base (variable, expr, loc)
521 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
522 ref Parameter parameter, TransparentIdentifiersScope ti)
524 args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
527 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
529 return new RangeAnonymousTypeParameter (expr, parameter);
532 protected override string MethodName {
533 get { return "Select"; }
537 public class Select : AQueryClause
539 public Select (Expression expr, Location loc)
545 // For queries like `from a orderby a select a'
546 // the projection is transparent and select clause can be safely removed
548 public bool IsRequired (Parameter parameter)
550 SimpleName sn = expr as SimpleName;
554 return sn.Name != parameter.Name;
557 protected override string MethodName {
558 get { return "Select"; }
562 public class SelectMany : ARangeVariableQueryClause
564 public SelectMany (LocatedToken variable, Expression expr)
565 : base (variable, expr, expr.Location)
569 protected override string MethodName {
570 get { return "SelectMany"; }
573 public override AQueryClause Next {
575 element_selector = value.expr;
577 // Can be optimized as SelectMany element selector
586 public class Where : AQueryClause
588 public Where (Expression expr, Location loc)
593 protected override string MethodName {
594 get { return "Where"; }
598 public class OrderByAscending : AQueryClause
600 public OrderByAscending (Expression expr)
601 : base (expr, expr.Location)
605 protected override string MethodName {
606 get { return "OrderBy"; }
610 public class OrderByDescending : AQueryClause
612 public OrderByDescending (Expression expr)
613 : base (expr, expr.Location)
617 protected override string MethodName {
618 get { return "OrderByDescending"; }
622 public class ThenByAscending : OrderByAscending
624 public ThenByAscending (Expression expr)
629 protected override string MethodName {
630 get { return "ThenBy"; }
634 public class ThenByDescending : OrderByDescending
636 public ThenByDescending (Expression expr)
641 protected override string MethodName {
642 get { return "ThenByDescending"; }
646 class ImplicitQueryParameter : ImplicitLambdaParameter
648 public sealed class ImplicitType : Expression
650 public static ImplicitType Instance = new ImplicitType ();
652 private ImplicitType ()
656 protected override void CloneTo (CloneContext clonectx, Expression target)
661 public override Expression CreateExpressionTree (EmitContext ec)
663 throw new NotSupportedException ();
666 public override Expression DoResolve (EmitContext ec)
668 throw new NotSupportedException ();
671 public override void Emit (EmitContext ec)
673 throw new NotSupportedException ();
677 public ImplicitQueryParameter (LocatedToken variable)
678 : base (variable.Value, variable.Location)
684 // Transparent parameters are used to package up the intermediate results
685 // and pass them onto next clause
687 public class TransparentParameter : ImplicitLambdaParameter
690 const string ParameterNamePrefix = "<>__TranspIdent";
692 public TransparentParameter (Location loc)
693 : base (ParameterNamePrefix + counter++, loc)
699 // Transparent identifiers are stored in nested anonymous types, each type can contain
700 // up to 2 identifiers or 1 identifier and parent type.
702 public class TransparentIdentifiersScope
704 readonly string [] identifiers;
705 readonly TransparentIdentifiersScope parent;
706 readonly TransparentParameter parameter;
708 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
709 TransparentParameter parameter, string [] identifiers)
711 this.parent = parent;
712 this.parameter = parameter;
713 this.identifiers = identifiers;
716 public MemberAccess GetIdentifier (string name)
718 TransparentIdentifiersScope ident = FindIdentifier (name);
722 return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
725 TransparentIdentifiersScope FindIdentifier (string name)
727 foreach (string s in identifiers) {
735 return parent.FindIdentifier (name);
738 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
740 Expression expr = new SimpleName (parameter.Name, parameter.Location);
741 TransparentIdentifiersScope current = this;
742 while (current != end)
744 current = current.parent;
745 expr = new MemberAccess (expr, current.parameter.Name);
753 // Lambda expression block which contains transparent identifiers
755 class SelectorBlock : ToplevelBlock
757 readonly TransparentIdentifiersScope transparent_identifiers;
759 public SelectorBlock (Block block, Parameters parameters,
760 TransparentIdentifiersScope transparentIdentifiers, Location loc)
761 : base (block, parameters, loc)
763 this.transparent_identifiers = transparentIdentifiers;
766 public override Expression GetTransparentIdentifier (string name)
768 Expression expr = null;
769 if (transparent_identifiers != null)
770 expr = transparent_identifiers.GetIdentifier (name);
772 if (expr != null || Container == null)
775 return Container.GetTransparentIdentifier (name);
780 // This block is actually never used, it is used by parser only
782 public class QueryBlock : Block
784 Hashtable range_variables = new Hashtable ();
786 public QueryBlock (Block parent, Location start)
787 : base (parent, start, Location.Null)
791 protected override void AddVariable (LocalInfo li)
793 string name = li.Name;
794 if (range_variables.Contains (name)) {
795 Location conflict = (Location)range_variables [name];
796 Report.SymbolRelatedToPreviousError (conflict, name);
797 Error_AlreadyDeclared (li.Location, name);
801 range_variables.Add (name, li.Location);
804 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
806 Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
810 protected override void Error_AlreadyDeclared (Location loc, string var)
812 Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
816 protected override void Error_AlreadyDeclaredTypeParameter (Location loc, string name)
818 Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",