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, MethodGroupExpr.IErrorHandler
78 public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
79 : base (expr, arguments)
83 protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
85 MethodGroupExpr.CustomErrorHandler = this;
86 MethodGroupExpr rmg = mg.OverloadResolve (ec, Arguments, false, loc);
87 MethodGroupExpr.CustomErrorHandler = null;
91 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",
123 public AQueryClause next;
124 /*protected*/ public Expression expr;
126 protected AQueryClause (Expression expr, Location loc)
132 protected override void CloneTo (CloneContext clonectx, Expression target)
134 AQueryClause t = (AQueryClause) target;
136 t.expr = expr.Clone (clonectx);
139 t.next = (AQueryClause)next.Clone (clonectx);
142 public override Expression DoResolve (EmitContext ec)
144 return expr.DoResolve (ec);
147 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
149 ArrayList args = new ArrayList (1);
150 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
151 lSide = CreateQueryExpression (lSide, args);
153 Select s = next as Select;
154 if (s == null || s.IsRequired (parameter))
155 return next.BuildQueryClause (ec, lSide, parameter, ti);
157 // Skip transparent select clause if any clause follows
158 if (next.next != null)
159 return next.next.BuildQueryClause (ec, lSide, parameter, ti);
165 protected static Parameter CreateBlockParameter (LocatedToken li)
167 return new ImplicitQueryParameter (li);
170 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
172 return new QueryExpressionInvocation (
173 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
176 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
178 return new QueryExpressionInvocation (
179 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
182 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter parameter, TransparentIdentifiersScope ti)
184 return CreateSelectorArgument (ec, expr, new Parameter [] { parameter }, ti);
187 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter[] parameters, TransparentIdentifiersScope ti)
189 Parameters p = new Parameters (parameters);
191 LambdaExpression selector = new LambdaExpression (
192 null, null, (TypeContainer)ec.TypeContainer, p, ec.CurrentBlock, loc);
193 selector.Block = new SelectorBlock (ec.CurrentBlock, p, ti, loc);
194 selector.Block.AddStatement (new Return (expr, loc));
196 if (!ec.IsInProbingMode) {
197 selector.CreateAnonymousHelpers ();
199 // TODO: I am not sure where this should be done to work
200 // correctly with anonymous containerss and was called only once
201 // FIXME: selector.RootScope == null for nested anonymous
203 if (selector.RootScope != null)
204 selector.RootScope.DefineType ();
207 return new Argument (selector);
210 public override void Emit (EmitContext ec)
212 throw new NotSupportedException ();
215 protected abstract string MethodName { get; }
217 public virtual AQueryClause Next {
223 public AQueryClause Tail {
225 return next == null ? this : next.Tail;
231 // A query clause with an identifier (range variable)
233 public abstract class ARangeVariableQueryClause : AQueryClause
235 LocatedToken variable;
236 protected Expression element_selector;
238 protected ARangeVariableQueryClause (LocatedToken variable, Expression expr, Location loc)
241 this.variable = variable;
244 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
245 ref Parameter parameter, TransparentIdentifiersScope ti)
247 args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
248 args.Add (CreateSelectorArgument (ec, element_selector,
249 new Parameter [] { parentParameter, parameter }, ti));
253 // Customization for range variables which not only creates a lambda expression but
254 // also builds a chain of range varible pairs
256 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
258 Parameter parameter = CreateBlockParameter (variable);
262 // Builds transparent identifiers, each identifier includes its parent
263 // type at index 0, and new value at index 1. This is not valid for the
264 // first one which includes two values directly.
266 ArrayList transp_args = new ArrayList (2);
267 transp_args.Add (new AnonymousTypeParameter (parentParameter));
268 transp_args.Add (CreateAnonymousTypeVariable (parameter));
269 element_selector = new AnonymousTypeDeclaration (transp_args, (TypeContainer) ec.TypeContainer, loc);
272 ArrayList args = new ArrayList ();
273 AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
275 lSide = CreateQueryExpression (lSide, args);
278 // Parameter identifiers go to the scope
280 string[] identifiers;
282 identifiers = new string [] { parentParameter.Name, parameter.Name };
284 identifiers = new string [] { parameter.Name };
287 TransparentParameter tp = new TransparentParameter (loc);
288 return next.BuildQueryClause (ec, lSide, tp,
289 new TransparentIdentifiersScope (ti, tp, identifiers));
295 protected override void CloneTo (CloneContext clonectx, Expression target)
297 ARangeVariableQueryClause t = (ARangeVariableQueryClause) target;
298 if (element_selector != null)
299 t.element_selector = element_selector.Clone (clonectx);
300 base.CloneTo (clonectx, t);
304 // For transparent identifiers, creates an instance of variable expression
306 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
308 return new AnonymousTypeParameter (parameter);
312 public class QueryStartClause : AQueryClause
314 public QueryStartClause (Expression expr)
315 : base (expr, expr.Location)
319 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
321 return next.BuildQueryClause (ec, expr, parameter, ti);
324 public override Expression DoResolve (EmitContext ec)
326 Expression e = BuildQueryClause (ec, null, null, null);
327 return e.Resolve (ec);
330 protected override string MethodName {
331 get { throw new NotSupportedException (); }
335 public class Cast : QueryStartClause
337 // We don't have to clone cast type
338 readonly Expression type_expr;
340 public Cast (Expression type, Expression expr)
343 this.type_expr = type;
346 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
348 lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
350 return next.BuildQueryClause (ec, lSide, parameter, ti);
355 protected override string MethodName {
356 get { return "Cast"; }
360 public class GroupBy : AQueryClause
362 Expression element_selector;
364 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
365 : base (keySelector, loc)
367 this.element_selector = elementSelector;
370 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
372 ArrayList args = new ArrayList (2);
373 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
375 // A query can be optimized when selector is not group by specific
376 if (!element_selector.Equals (lSide))
377 args.Add (CreateSelectorArgument (ec, element_selector, parameter, ti));
379 lSide = CreateQueryExpression (lSide, args);
381 return next.BuildQueryClause (ec, lSide, parameter, ti);
386 protected override void CloneTo (CloneContext clonectx, Expression target)
388 GroupBy t = (GroupBy) target;
389 t.element_selector = element_selector.Clone (clonectx);
390 base.CloneTo (clonectx, t);
393 protected override string MethodName {
394 get { return "GroupBy"; }
398 public class Join : ARangeVariableQueryClause
400 Expression projection;
401 Expression inner_selector, outer_selector;
403 public Join (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
404 : base (variable, inner, loc)
406 this.outer_selector = outerSelector;
407 this.inner_selector = innerSelector;
410 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
411 ref Parameter parameter, TransparentIdentifiersScope ti)
413 args.Add (new Argument (expr));
414 args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
415 args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
417 parameter = CreateResultSelectorParameter (parameter);
418 if (projection == null) {
419 ArrayList join_args = new ArrayList (2);
420 join_args.Add (new AnonymousTypeParameter (parentParameter));
421 join_args.Add (new AnonymousTypeParameter (parameter));
422 projection = new AnonymousTypeDeclaration (join_args, (TypeContainer) ec.TypeContainer, loc);
425 args.Add (CreateSelectorArgument (ec, projection,
426 new Parameter [] { parentParameter, parameter }, ti));
429 protected override void CloneTo (CloneContext clonectx, Expression target)
431 Join t = (Join) target;
432 t.projection = projection.Clone (clonectx);
433 t.inner_selector = inner_selector.Clone (clonectx);
434 t.outer_selector = outer_selector.Clone (clonectx);
435 base.CloneTo (clonectx, t);
438 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
443 public override AQueryClause Next {
445 // Use select as join projection
446 if (value is Select) {
447 projection = value.expr;
456 protected override string MethodName {
457 get { return "Join"; }
461 public class GroupJoin : Join
463 readonly LocatedToken into_variable;
465 public GroupJoin (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector,
466 LocatedToken into, Location loc)
467 : base (variable, inner, outerSelector, innerSelector, loc)
469 this.into_variable = into;
472 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
475 // into variable is used as result selector and it's passed as
476 // transparent identifiers to the next clause
478 return CreateBlockParameter (into_variable);
481 protected override string MethodName {
482 get { return "GroupJoin"; }
486 public class Let : ARangeVariableQueryClause
488 class RangeAnonymousTypeParameter : AnonymousTypeParameter
490 readonly Parameter parameter;
492 public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
493 : base (initializer, parameter.Name, parameter.Location)
495 this.parameter = parameter;
498 public override Expression DoResolve (EmitContext ec)
500 Expression e = base.DoResolve (ec);
503 // Spread resolved initializer type
505 parameter.ParameterType = type;
506 parameter.Resolve (ec);
512 protected override void Error_InvalidInitializer (Expression initializer)
514 Report.Error (1932, loc, "A range variable `{0}' cannot be initialized with `{1}'",
515 Name, initializer.GetSignatureForError ());
519 public Let (LocatedToken variable, Expression expr, Location loc)
520 : base (variable, expr, loc)
524 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
525 ref Parameter parameter, TransparentIdentifiersScope ti)
527 args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
530 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
532 return new RangeAnonymousTypeParameter (expr, parameter);
535 protected override string MethodName {
536 get { return "Select"; }
540 public class Select : AQueryClause
542 public Select (Expression expr, Location loc)
548 // For queries like `from a orderby a select a'
549 // the projection is transparent and select clause can be safely removed
551 public bool IsRequired (Parameter parameter)
553 SimpleName sn = expr as SimpleName;
557 return sn.Name != parameter.Name;
560 protected override string MethodName {
561 get { return "Select"; }
565 public class SelectMany : ARangeVariableQueryClause
567 public SelectMany (LocatedToken variable, Expression expr)
568 : base (variable, expr, expr.Location)
572 protected override string MethodName {
573 get { return "SelectMany"; }
576 public override AQueryClause Next {
578 element_selector = value.expr;
580 // Can be optimized as SelectMany element selector
589 public class Where : AQueryClause
591 public Where (Expression expr, Location loc)
596 protected override string MethodName {
597 get { return "Where"; }
601 public class OrderByAscending : AQueryClause
603 public OrderByAscending (Expression expr)
604 : base (expr, expr.Location)
608 protected override string MethodName {
609 get { return "OrderBy"; }
613 public class OrderByDescending : AQueryClause
615 public OrderByDescending (Expression expr)
616 : base (expr, expr.Location)
620 protected override string MethodName {
621 get { return "OrderByDescending"; }
625 public class ThenByAscending : OrderByAscending
627 public ThenByAscending (Expression expr)
632 protected override string MethodName {
633 get { return "ThenBy"; }
637 public class ThenByDescending : OrderByDescending
639 public ThenByDescending (Expression expr)
644 protected override string MethodName {
645 get { return "ThenByDescending"; }
649 class ImplicitQueryParameter : ImplicitLambdaParameter
651 public sealed class ImplicitType : Expression
653 public static ImplicitType Instance = new ImplicitType ();
655 private ImplicitType ()
659 protected override void CloneTo (CloneContext clonectx, Expression target)
664 public override Expression DoResolve (EmitContext ec)
666 throw new NotSupportedException ();
669 public override void Emit (EmitContext ec)
671 throw new NotSupportedException ();
675 public ImplicitQueryParameter (LocatedToken variable)
676 : base (variable.Value, variable.Location)
682 // Transparent parameters are used to package up the intermediate results
683 // and pass them onto next clause
685 public class TransparentParameter : ImplicitLambdaParameter
688 const string ParameterNamePrefix = "<>__TranspIdent";
690 public TransparentParameter (Location loc)
691 : base (ParameterNamePrefix + counter++, loc)
697 // Transparent identifiers are stored in nested anonymous types, each type can contain
698 // up to 2 identifiers or 1 identifier and parent type.
700 public class TransparentIdentifiersScope
702 readonly string [] identifiers;
703 readonly TransparentIdentifiersScope parent;
704 readonly TransparentParameter parameter;
706 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
707 TransparentParameter parameter, string [] identifiers)
709 this.parent = parent;
710 this.parameter = parameter;
711 this.identifiers = identifiers;
714 public MemberAccess GetIdentifier (string name)
716 TransparentIdentifiersScope ident = FindIdentifier (name);
720 return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
723 TransparentIdentifiersScope FindIdentifier (string name)
725 foreach (string s in identifiers) {
733 return parent.FindIdentifier (name);
736 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
738 Expression expr = new SimpleName (parameter.Name, parameter.Location);
739 TransparentIdentifiersScope current = this;
740 while (current != end)
742 current = current.parent;
743 expr = new MemberAccess (expr, current.parameter.Name);
751 // Lambda expression block which contains transparent identifiers
753 class SelectorBlock : ToplevelBlock
755 readonly TransparentIdentifiersScope transparent_identifiers;
757 public SelectorBlock (Block block, Parameters parameters,
758 TransparentIdentifiersScope transparentIdentifiers, Location loc)
759 : base (block, parameters, loc)
761 this.transparent_identifiers = transparentIdentifiers;
764 public override Expression GetTransparentIdentifier (string name)
766 Expression expr = null;
767 if (transparent_identifiers != null)
768 expr = transparent_identifiers.GetIdentifier (name);
770 if (expr != null || Container == null)
773 return Container.GetTransparentIdentifier (name);
778 // This block is actually never used, it is used by parser only
780 public class QueryBlock : Block
782 Hashtable range_variables = new Hashtable ();
784 public QueryBlock (Block parent, Location start)
785 : base (parent, start, Location.Null)
789 protected override void AddVariable (LocalInfo li)
791 string name = li.Name;
792 if (range_variables.Contains (name)) {
793 Location conflict = (Location)range_variables [name];
794 Report.SymbolRelatedToPreviousError (conflict, name);
795 Error_AlreadyDeclared (li.Location, name);
799 range_variables.Add (name, li.Location);
802 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
804 Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
808 protected override void Error_AlreadyDeclared (Location loc, string var)
810 Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
814 protected override void Error_AlreadyDeclaredTypeParameter (Location loc, string name)
816 Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",