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 (
201 null, null, (TypeContainer)ec.TypeContainer, p, ec.CurrentBlock, loc);
202 selector.Block = new SelectorBlock (ec.CurrentBlock, p, ti, loc);
203 selector.Block.AddStatement (new ContextualReturn (expr));
205 if (!ec.IsInProbingMode) {
206 selector.CreateAnonymousHelpers ();
208 // TODO: I am not sure where this should be done to work
209 // correctly with anonymous containerss and was called only once
210 // FIXME: selector.RootScope == null for nested anonymous
212 if (selector.RootScope != null)
213 selector.RootScope.DefineType ();
216 return new Argument (selector);
219 public override void Emit (EmitContext ec)
221 throw new NotSupportedException ();
224 protected abstract string MethodName { get; }
226 public virtual AQueryClause Next {
232 public AQueryClause Tail {
234 return next == null ? this : next.Tail;
240 // A query clause with an identifier (range variable)
242 public abstract class ARangeVariableQueryClause : AQueryClause
244 LocatedToken variable;
245 protected Expression element_selector;
247 protected ARangeVariableQueryClause (LocatedToken variable, Expression expr, Location loc)
250 this.variable = variable;
253 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
254 ref Parameter parameter, TransparentIdentifiersScope ti)
256 args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
257 args.Add (CreateSelectorArgument (ec, element_selector,
258 new Parameter [] { parentParameter, parameter }, ti));
262 // Customization for range variables which not only creates a lambda expression but
263 // also builds a chain of range varible pairs
265 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
267 Parameter parameter = CreateBlockParameter (variable);
271 // Builds transparent identifiers, each identifier includes its parent
272 // type at index 0, and new value at index 1. This is not valid for the
273 // first one which includes two values directly.
275 ArrayList transp_args = new ArrayList (2);
276 transp_args.Add (new AnonymousTypeParameter (parentParameter));
277 transp_args.Add (CreateAnonymousTypeVariable (parameter));
278 element_selector = new AnonymousTypeDeclaration (transp_args, (TypeContainer) ec.TypeContainer, loc);
281 ArrayList args = new ArrayList ();
282 AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
284 lSide = CreateQueryExpression (lSide, args);
287 // Parameter identifiers go to the scope
289 string[] identifiers;
291 identifiers = new string [] { parentParameter.Name, parameter.Name };
293 identifiers = new string [] { parameter.Name };
296 TransparentParameter tp = new TransparentParameter (loc);
297 return next.BuildQueryClause (ec, lSide, tp,
298 new TransparentIdentifiersScope (ti, tp, identifiers));
304 protected override void CloneTo (CloneContext clonectx, Expression target)
306 ARangeVariableQueryClause t = (ARangeVariableQueryClause) target;
307 if (element_selector != null)
308 t.element_selector = element_selector.Clone (clonectx);
309 base.CloneTo (clonectx, t);
313 // For transparent identifiers, creates an instance of variable expression
315 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
317 return new AnonymousTypeParameter (parameter);
321 public class QueryStartClause : AQueryClause
323 public QueryStartClause (Expression expr)
324 : base (expr, expr.Location)
328 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
330 return next.BuildQueryClause (ec, expr, parameter, ti);
333 public override Expression DoResolve (EmitContext ec)
335 Expression e = BuildQueryClause (ec, null, null, null);
336 return e.Resolve (ec);
339 protected override string MethodName {
340 get { throw new NotSupportedException (); }
344 public class Cast : QueryStartClause
346 // We don't have to clone cast type
347 readonly Expression type_expr;
349 public Cast (Expression type, Expression expr)
352 this.type_expr = type;
355 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
357 lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
359 return next.BuildQueryClause (ec, lSide, parameter, ti);
364 protected override string MethodName {
365 get { return "Cast"; }
369 public class GroupBy : AQueryClause
371 Expression element_selector;
373 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
374 : base (keySelector, loc)
376 this.element_selector = elementSelector;
379 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
381 ArrayList args = new ArrayList (2);
382 args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
384 // A query can be optimized when selector is not group by specific
385 if (!element_selector.Equals (lSide))
386 args.Add (CreateSelectorArgument (ec, element_selector, parameter, ti));
388 lSide = CreateQueryExpression (lSide, args);
390 return next.BuildQueryClause (ec, lSide, parameter, ti);
395 protected override void CloneTo (CloneContext clonectx, Expression target)
397 GroupBy t = (GroupBy) target;
398 t.element_selector = element_selector.Clone (clonectx);
399 base.CloneTo (clonectx, t);
402 protected override string MethodName {
403 get { return "GroupBy"; }
407 public class Join : ARangeVariableQueryClause
409 Expression projection;
410 Expression inner_selector, outer_selector;
412 public Join (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
413 : base (variable, inner, loc)
415 this.outer_selector = outerSelector;
416 this.inner_selector = innerSelector;
419 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
420 ref Parameter parameter, TransparentIdentifiersScope ti)
422 args.Add (new Argument (expr));
423 args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
424 args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
426 parameter = CreateResultSelectorParameter (parameter);
427 if (projection == null) {
428 ArrayList join_args = new ArrayList (2);
429 join_args.Add (new AnonymousTypeParameter (parentParameter));
430 join_args.Add (new AnonymousTypeParameter (parameter));
431 projection = new AnonymousTypeDeclaration (join_args, (TypeContainer) ec.TypeContainer, loc);
434 args.Add (CreateSelectorArgument (ec, projection,
435 new Parameter [] { parentParameter, parameter }, ti));
438 protected override void CloneTo (CloneContext clonectx, Expression target)
440 Join t = (Join) target;
441 t.projection = projection.Clone (clonectx);
442 t.inner_selector = inner_selector.Clone (clonectx);
443 t.outer_selector = outer_selector.Clone (clonectx);
444 base.CloneTo (clonectx, t);
447 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
452 public override AQueryClause Next {
454 // Use select as join projection
455 if (value is Select) {
456 projection = value.expr;
465 protected override string MethodName {
466 get { return "Join"; }
470 public class GroupJoin : Join
472 readonly LocatedToken into_variable;
474 public GroupJoin (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector,
475 LocatedToken into, Location loc)
476 : base (variable, inner, outerSelector, innerSelector, loc)
478 this.into_variable = into;
481 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
484 // into variable is used as result selector and it's passed as
485 // transparent identifiers to the next clause
487 return CreateBlockParameter (into_variable);
490 protected override string MethodName {
491 get { return "GroupJoin"; }
495 public class Let : ARangeVariableQueryClause
497 class RangeAnonymousTypeParameter : AnonymousTypeParameter
499 readonly Parameter parameter;
501 public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
502 : base (initializer, parameter.Name, parameter.Location)
504 this.parameter = parameter;
507 public override Expression DoResolve (EmitContext ec)
509 Expression e = base.DoResolve (ec);
512 // Spread resolved initializer type
514 parameter.ParameterType = type;
515 parameter.Resolve (ec);
521 protected override void Error_InvalidInitializer (Expression initializer)
523 Report.Error (1932, loc, "A range variable `{0}' cannot be initialized with `{1}'",
524 Name, initializer.GetSignatureForError ());
528 public Let (LocatedToken variable, Expression expr, Location loc)
529 : base (variable, expr, loc)
533 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
534 ref Parameter parameter, TransparentIdentifiersScope ti)
536 args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
539 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
541 return new RangeAnonymousTypeParameter (expr, parameter);
544 protected override string MethodName {
545 get { return "Select"; }
549 public class Select : AQueryClause
551 public Select (Expression expr, Location loc)
557 // For queries like `from a orderby a select a'
558 // the projection is transparent and select clause can be safely removed
560 public bool IsRequired (Parameter parameter)
562 SimpleName sn = expr as SimpleName;
566 return sn.Name != parameter.Name;
569 protected override string MethodName {
570 get { return "Select"; }
574 public class SelectMany : ARangeVariableQueryClause
576 public SelectMany (LocatedToken variable, Expression expr)
577 : base (variable, expr, expr.Location)
581 protected override string MethodName {
582 get { return "SelectMany"; }
585 public override AQueryClause Next {
587 element_selector = value.expr;
589 // Can be optimized as SelectMany element selector
598 public class Where : AQueryClause
600 public Where (Expression expr, Location loc)
605 protected override string MethodName {
606 get { return "Where"; }
610 public class OrderByAscending : AQueryClause
612 public OrderByAscending (Expression expr)
613 : base (expr, expr.Location)
617 protected override string MethodName {
618 get { return "OrderBy"; }
622 public class OrderByDescending : AQueryClause
624 public OrderByDescending (Expression expr)
625 : base (expr, expr.Location)
629 protected override string MethodName {
630 get { return "OrderByDescending"; }
634 public class ThenByAscending : OrderByAscending
636 public ThenByAscending (Expression expr)
641 protected override string MethodName {
642 get { return "ThenBy"; }
646 public class ThenByDescending : OrderByDescending
648 public ThenByDescending (Expression expr)
653 protected override string MethodName {
654 get { return "ThenByDescending"; }
658 class ImplicitQueryParameter : ImplicitLambdaParameter
660 public sealed class ImplicitType : Expression
662 public static ImplicitType Instance = new ImplicitType ();
664 private ImplicitType ()
668 protected override void CloneTo (CloneContext clonectx, Expression target)
673 public override Expression CreateExpressionTree (EmitContext ec)
675 throw new NotSupportedException ();
678 public override Expression DoResolve (EmitContext ec)
680 throw new NotSupportedException ();
683 public override void Emit (EmitContext ec)
685 throw new NotSupportedException ();
689 public ImplicitQueryParameter (LocatedToken variable)
690 : base (variable.Value, variable.Location)
696 // Transparent parameters are used to package up the intermediate results
697 // and pass them onto next clause
699 public class TransparentParameter : ImplicitLambdaParameter
702 const string ParameterNamePrefix = "<>__TranspIdent";
704 public TransparentParameter (Location loc)
705 : base (ParameterNamePrefix + counter++, loc)
711 // Transparent identifiers are stored in nested anonymous types, each type can contain
712 // up to 2 identifiers or 1 identifier and parent type.
714 public class TransparentIdentifiersScope
716 readonly string [] identifiers;
717 readonly TransparentIdentifiersScope parent;
718 readonly TransparentParameter parameter;
720 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
721 TransparentParameter parameter, string [] identifiers)
723 this.parent = parent;
724 this.parameter = parameter;
725 this.identifiers = identifiers;
728 public MemberAccess GetIdentifier (string name)
730 TransparentIdentifiersScope ident = FindIdentifier (name);
734 return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
737 TransparentIdentifiersScope FindIdentifier (string name)
739 foreach (string s in identifiers) {
747 return parent.FindIdentifier (name);
750 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
752 Expression expr = new SimpleName (parameter.Name, parameter.Location);
753 TransparentIdentifiersScope current = this;
754 while (current != end)
756 current = current.parent;
757 expr = new MemberAccess (expr, current.parameter.Name);
765 // Lambda expression block which contains transparent identifiers
767 class SelectorBlock : ToplevelBlock
769 readonly TransparentIdentifiersScope transparent_identifiers;
771 public SelectorBlock (Block block, Parameters parameters,
772 TransparentIdentifiersScope transparentIdentifiers, Location loc)
773 : base (block, parameters, loc)
775 this.transparent_identifiers = transparentIdentifiers;
778 public override Expression GetTransparentIdentifier (string name)
780 Expression expr = null;
781 if (transparent_identifiers != null)
782 expr = transparent_identifiers.GetIdentifier (name);
784 if (expr != null || Container == null)
787 return Container.GetTransparentIdentifier (name);
792 // This block is actually never used, it is used by parser only
794 public class QueryBlock : Block
796 Hashtable range_variables = new Hashtable ();
798 public QueryBlock (Block parent, Location start)
799 : base (parent, start, Location.Null)
803 protected override void AddVariable (LocalInfo li)
805 string name = li.Name;
806 if (range_variables.Contains (name)) {
807 Location conflict = (Location)range_variables [name];
808 Report.SymbolRelatedToPreviousError (conflict, name);
809 Error_AlreadyDeclared (li.Location, name);
813 range_variables.Add (name, li.Location);
816 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
818 Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
822 protected override void Error_AlreadyDeclared (Location loc, string var)
824 Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
828 protected override void Error_AlreadyDeclaredTypeParameter (Location loc, string name)
830 Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",