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
.Generic
;
15 namespace Mono
.CSharp
.Linq
17 public class QueryExpression
: AQueryClause
19 public QueryExpression (AQueryClause start
)
20 : base (null, null, Location
.Null
)
25 public override Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
, Parameter parentParameter
)
27 return next
.BuildQueryClause (ec
, lSide
, parentParameter
);
30 protected override Expression
DoResolve (ResolveContext ec
)
32 int counter
= QueryBlock
.TransparentParameter
.Counter
;
34 Expression e
= BuildQueryClause (ec
, null, null);
39 // Reset counter in probing mode to ensure that all transparent
40 // identifier anonymous types are created only once
42 if (ec
.IsInProbingMode
)
43 QueryBlock
.TransparentParameter
.Counter
= counter
;
48 protected override string MethodName
{
49 get { throw new NotSupportedException (); }
53 public abstract class AQueryClause
: ShimExpression
55 protected class QueryExpressionAccess
: MemberAccess
57 public QueryExpressionAccess (Expression expr
, string methodName
, Location loc
)
58 : base (expr
, methodName
, loc
)
62 public QueryExpressionAccess (Expression expr
, string methodName
, TypeArguments typeArguments
, Location loc
)
63 : base (expr
, methodName
, typeArguments
, loc
)
67 protected override Expression
Error_MemberLookupFailed (ResolveContext ec
, TypeSpec container_type
, TypeSpec qualifier_type
,
68 TypeSpec queried_type
, string name
, int arity
, string class_name
, MemberKind mt
, BindingRestriction bf
)
70 ec
.Report
.Error (1935, loc
, "An implementation of `{0}' query expression pattern could not be found. " +
71 "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
77 protected class QueryExpressionInvocation
: Invocation
, MethodGroupExpr
.IErrorHandler
79 public QueryExpressionInvocation (QueryExpressionAccess expr
, Arguments arguments
)
80 : base (expr
, arguments
)
84 protected override MethodGroupExpr
DoResolveOverload (ResolveContext ec
)
86 mg
.CustomErrorHandler
= this;
87 MethodGroupExpr rmg
= mg
.OverloadResolve (ec
, ref arguments
, false, loc
);
91 public bool AmbiguousCall (ResolveContext ec
, MethodSpec ambiguous
)
93 ec
.Report
.SymbolRelatedToPreviousError (mg
.BestCandidate
);
94 ec
.Report
.SymbolRelatedToPreviousError (ambiguous
);
95 ec
.Report
.Error (1940, loc
, "Ambiguous implementation of the query pattern `{0}' for source type `{1}'",
96 mg
.Name
, mg
.InstanceExpression
.GetSignatureForError ());
100 public bool NoExactMatch (ResolveContext ec
, MethodSpec method
)
102 var pd
= method
.Parameters
;
103 TypeSpec source_type
= pd
.ExtensionMethodType
;
104 if (source_type
!= null) {
105 Argument a
= arguments
[0];
107 if (TypeManager
.IsGenericType (source_type
) && TypeManager
.ContainsGenericParameters (source_type
)) {
108 TypeInferenceContext tic
= new TypeInferenceContext (source_type
.TypeArguments
);
109 tic
.OutputTypeInference (ec
, a
.Expr
, source_type
);
110 if (tic
.FixAllTypes (ec
)) {
111 source_type
= source_type
.GetDefinition ().MakeGenericType (tic
.InferredTypeArguments
);
115 if (!Convert
.ImplicitConversionExists (ec
, a
.Expr
, source_type
)) {
116 ec
.Report
.Error (1936, loc
, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
117 mg
.Name
, TypeManager
.CSharpName (a
.Type
));
122 if (!method
.IsGeneric
)
125 if (mg
.Name
== "SelectMany") {
126 ec
.Report
.Error (1943, loc
,
127 "An expression type is incorrect in a subsequent `from' clause in a query expression with source type `{0}'",
128 arguments
[0].GetSignatureForError ());
130 ec
.Report
.Error (1942, loc
,
131 "An expression type in `{0}' clause is incorrect. Type inference failed in the call to `{1}'",
132 mg
.Name
.ToLower (), mg
.Name
);
139 public AQueryClause next
;
140 public QueryBlock block
;
142 protected AQueryClause (QueryBlock block
, Expression expr
, Location loc
)
149 protected override void CloneTo (CloneContext clonectx
, Expression target
)
151 base.CloneTo (clonectx
, target
);
153 AQueryClause t
= (AQueryClause
) target
;
156 t
.block
= (QueryBlock
) clonectx
.LookupBlock (block
);
159 t
.next
= (AQueryClause
) next
.Clone (clonectx
);
162 protected override Expression
DoResolve (ResolveContext ec
)
164 return expr
.Resolve (ec
);
167 public virtual Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
, Parameter parameter
)
169 Arguments args
= null;
170 CreateArguments (ec
, parameter
, ref args
);
171 lSide
= CreateQueryExpression (lSide
, args
);
173 parameter
= CreateChildrenParameters (parameter
);
175 Select s
= next
as Select
;
176 if (s
== null || s
.IsRequired (parameter
))
177 return next
.BuildQueryClause (ec
, lSide
, parameter
);
179 // Skip transparent select clause if any clause follows
180 if (next
.next
!= null)
181 return next
.next
.BuildQueryClause (ec
, lSide
, parameter
);
187 protected virtual Parameter
CreateChildrenParameters (Parameter parameter
)
192 protected virtual void CreateArguments (ResolveContext ec
, Parameter parameter
, ref Arguments args
)
194 args
= new Arguments (2);
196 LambdaExpression selector
= new LambdaExpression (loc
);
198 block
.SetParameter (parameter
.Clone ());
199 selector
.Block
= block
;
200 selector
.Block
.AddStatement (new ContextualReturn (expr
));
202 args
.Add (new Argument (selector
));
205 protected Invocation
CreateQueryExpression (Expression lSide
, Arguments arguments
)
207 return new QueryExpressionInvocation (
208 new QueryExpressionAccess (lSide
, MethodName
, loc
), arguments
);
211 protected abstract string MethodName { get; }
213 public AQueryClause Next
{
219 public AQueryClause Tail
{
221 return next
== null ? this : next
.Tail
;
227 // A query clause with an identifier (range variable)
229 public abstract class ARangeVariableQueryClause
: AQueryClause
231 sealed class RangeAnonymousTypeParameter
: AnonymousTypeParameter
233 public RangeAnonymousTypeParameter (Expression initializer
, SimpleMemberName parameter
)
234 : base (initializer
, parameter
.Value
, parameter
.Location
)
238 protected override void Error_InvalidInitializer (ResolveContext ec
, string initializer
)
240 ec
.Report
.Error (1932, loc
, "A range variable `{0}' cannot be initialized with `{1}'",
245 protected SimpleMemberName range_variable
;
247 protected ARangeVariableQueryClause (QueryBlock block
, SimpleMemberName identifier
, Expression expr
, Location loc
)
248 : base (block
, expr
, loc
)
250 range_variable
= identifier
;
253 public FullNamedExpression IdentifierType { get; set; }
255 protected Invocation
CreateCastExpression (Expression lSide
)
257 return new QueryExpressionInvocation (
258 new QueryExpressionAccess (lSide
, "Cast", new TypeArguments (IdentifierType
), loc
), null);
261 protected override Parameter
CreateChildrenParameters (Parameter parameter
)
263 return new QueryBlock
.TransparentParameter (parameter
, GetIntoVariable ());
266 protected static Expression
CreateRangeVariableType (ResolveContext rc
, Parameter parameter
, SimpleMemberName name
, Expression init
)
268 var args
= new List
<AnonymousTypeParameter
> (2);
269 args
.Add (new AnonymousTypeParameter (parameter
));
270 args
.Add (new RangeAnonymousTypeParameter (init
, name
));
271 return new NewAnonymousType (args
, rc
.MemberContext
.CurrentMemberDefinition
.Parent
, name
.Location
);
274 protected virtual SimpleMemberName
GetIntoVariable ()
276 return range_variable
;
280 class QueryStartClause
: ARangeVariableQueryClause
282 public QueryStartClause (QueryBlock block
, Expression expr
, SimpleMemberName identifier
, Location loc
)
283 : base (block
, identifier
, expr
, loc
)
285 block
.AddRangeVariable (identifier
);
288 public override Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
, Parameter parameter
)
291 expr = expr.Resolve (ec);
295 if (expr.Type == InternalType.Dynamic || expr.Type == TypeManager.void_type) {
296 ec.Report.Error (1979, expr.Location,
297 "Query expression with a source or join sequence of type `{0}' is not allowed",
298 TypeManager.CSharpName (expr.Type));
303 if (IdentifierType
!= null)
304 expr
= CreateCastExpression (expr
);
306 if (parameter
== null)
309 return next
.BuildQueryClause (ec
, lSide
, new ImplicitLambdaParameter (range_variable
.Value
, range_variable
.Location
));
312 protected override Expression
DoResolve (ResolveContext ec
)
314 Expression e
= BuildQueryClause (ec
, null, null);
315 return e
.Resolve (ec
);
318 protected override string MethodName
{
319 get { throw new NotSupportedException (); }
323 public class GroupBy
: AQueryClause
325 Expression element_selector
;
326 QueryBlock element_block
;
328 public GroupBy (QueryBlock block
, Expression elementSelector
, QueryBlock elementBlock
, Expression keySelector
, Location loc
)
329 : base (block
, keySelector
, loc
)
332 // Optimizes clauses like `group A by A'
334 if (!elementSelector
.Equals (keySelector
)) {
335 this.element_selector
= elementSelector
;
336 this.element_block
= elementBlock
;
340 protected override void CreateArguments (ResolveContext ec
, Parameter parameter
, ref Arguments args
)
342 base.CreateArguments (ec
, parameter
, ref args
);
344 if (element_selector
!= null) {
345 LambdaExpression lambda
= new LambdaExpression (element_selector
.Location
);
347 element_block
.SetParameter (parameter
.Clone ());
348 lambda
.Block
= element_block
;
349 lambda
.Block
.AddStatement (new ContextualReturn (element_selector
));
350 args
.Add (new Argument (lambda
));
354 protected override void CloneTo (CloneContext clonectx
, Expression target
)
356 GroupBy t
= (GroupBy
) target
;
357 if (element_selector
!= null) {
358 t
.element_selector
= element_selector
.Clone (clonectx
);
359 t
.element_block
= (QueryBlock
) element_block
.Clone (clonectx
);
362 base.CloneTo (clonectx
, t
);
365 protected override string MethodName
{
366 get { return "GroupBy"; }
370 public class Join
: SelectMany
372 QueryBlock inner_selector
, outer_selector
;
374 public Join (QueryBlock block
, SimpleMemberName lt
, Expression inner
, QueryBlock outerSelector
, QueryBlock innerSelector
, Location loc
)
375 : base (block
, lt
, inner
, loc
)
377 this.outer_selector
= outerSelector
;
378 this.inner_selector
= innerSelector
;
381 protected override void CreateArguments (ResolveContext ec
, Parameter parameter
, ref Arguments args
)
383 args
= new Arguments (4);
385 if (IdentifierType
!= null)
386 expr
= CreateCastExpression (expr
);
388 args
.Add (new Argument (expr
));
390 outer_selector
.SetParameter (parameter
.Clone ());
391 var lambda
= new LambdaExpression (outer_selector
.StartLocation
);
392 lambda
.Block
= outer_selector
;
393 args
.Add (new Argument (lambda
));
395 inner_selector
.SetParameter (new ImplicitLambdaParameter (range_variable
.Value
, range_variable
.Location
));
396 lambda
= new LambdaExpression (inner_selector
.StartLocation
);
397 lambda
.Block
= inner_selector
;
398 args
.Add (new Argument (lambda
));
400 base.CreateArguments (ec
, parameter
, ref args
);
403 protected override void CloneTo (CloneContext clonectx
, Expression target
)
405 Join t
= (Join
) target
;
406 t
.inner_selector
= (QueryBlock
) inner_selector
.Clone (clonectx
);
407 t
.outer_selector
= (QueryBlock
) outer_selector
.Clone (clonectx
);
408 base.CloneTo (clonectx
, t
);
411 protected override string MethodName
{
412 get { return "Join"; }
416 public class GroupJoin
: Join
418 readonly SimpleMemberName
into;
420 public GroupJoin (QueryBlock block
, SimpleMemberName lt
, Expression inner
,
421 QueryBlock outerSelector
, QueryBlock innerSelector
, SimpleMemberName
into, Location loc
)
422 : base (block
, lt
, inner
, outerSelector
, innerSelector
, loc
)
427 protected override SimpleMemberName
GetIntoVariable ()
432 protected override string MethodName
{
433 get { return "GroupJoin"; }
437 public class Let
: ARangeVariableQueryClause
439 public Let (QueryBlock block
, SimpleMemberName identifier
, Expression expr
, Location loc
)
440 : base (block
, identifier
, expr
, loc
)
444 protected override void CreateArguments (ResolveContext ec
, Parameter parameter
, ref Arguments args
)
446 expr
= CreateRangeVariableType (ec
, parameter
, range_variable
, expr
);
447 base.CreateArguments (ec
, parameter
, ref args
);
450 protected override string MethodName
{
451 get { return "Select"; }
455 public class Select
: AQueryClause
457 public Select (QueryBlock block
, Expression expr
, Location loc
)
458 : base (block
, expr
, loc
)
463 // For queries like `from a orderby a select a'
464 // the projection is transparent and select clause can be safely removed
466 public bool IsRequired (Parameter parameter
)
468 SimpleName sn
= expr
as SimpleName
;
472 return sn
.Name
!= parameter
.Name
;
475 protected override string MethodName
{
476 get { return "Select"; }
480 public class SelectMany
: ARangeVariableQueryClause
482 public SelectMany (QueryBlock block
, SimpleMemberName identifier
, Expression expr
, Location loc
)
483 : base (block
, identifier
, expr
, loc
)
487 protected override void CreateArguments (ResolveContext ec
, Parameter parameter
, ref Arguments args
)
490 if (IdentifierType
!= null)
491 expr
= CreateCastExpression (expr
);
493 base.CreateArguments (ec
, parameter
, ref args
);
496 Expression result_selector_expr
;
497 QueryBlock result_block
;
499 var target
= GetIntoVariable ();
500 var target_param
= new ImplicitLambdaParameter (target
.Value
, target
.Location
);
503 // When select follows use it as a result selector
505 if (next
is Select
) {
506 result_selector_expr
= next
.Expr
;
508 result_block
= next
.block
;
509 result_block
.SetParameters (parameter
, target_param
);
513 result_selector_expr
= CreateRangeVariableType (ec
, parameter
, target
, new SimpleName (target
.Value
, target
.Location
));
515 result_block
= new QueryBlock (ec
.Compiler
, block
.Parent
, block
.StartLocation
);
516 result_block
.SetParameters (parameter
, target_param
);
519 LambdaExpression result_selector
= new LambdaExpression (Location
);
520 result_selector
.Block
= result_block
;
521 result_selector
.Block
.AddStatement (new ContextualReturn (result_selector_expr
));
523 args
.Add (new Argument (result_selector
));
526 protected override string MethodName
{
527 get { return "SelectMany"; }
531 public class Where
: AQueryClause
533 public Where (QueryBlock block
, BooleanExpression expr
, Location loc
)
534 : base (block
, expr
, loc
)
538 protected override string MethodName
{
539 get { return "Where"; }
542 protected override void CreateArguments (ResolveContext ec
, Parameter parameter
, ref Arguments args
)
544 base.CreateArguments (ec
, parameter
, ref args
);
548 public class OrderByAscending
: AQueryClause
550 public OrderByAscending (QueryBlock block
, Expression expr
)
551 : base (block
, expr
, expr
.Location
)
555 protected override string MethodName
{
556 get { return "OrderBy"; }
560 public class OrderByDescending
: AQueryClause
562 public OrderByDescending (QueryBlock block
, Expression expr
)
563 : base (block
, expr
, expr
.Location
)
567 protected override string MethodName
{
568 get { return "OrderByDescending"; }
572 public class ThenByAscending
: OrderByAscending
574 public ThenByAscending (QueryBlock block
, Expression expr
)
579 protected override string MethodName
{
580 get { return "ThenBy"; }
584 public class ThenByDescending
: OrderByDescending
586 public ThenByDescending (QueryBlock block
, Expression expr
)
591 protected override string MethodName
{
592 get { return "ThenByDescending"; }
597 // Implicit query block
599 public class QueryBlock
: ToplevelBlock
602 // Transparent parameters are used to package up the intermediate results
603 // and pass them onto next clause
605 public sealed class TransparentParameter
: ImplicitLambdaParameter
607 public static int Counter
;
608 const string ParameterNamePrefix
= "<>__TranspIdent";
610 public readonly Parameter Parent
;
611 public readonly string Identifier
;
613 public TransparentParameter (Parameter parent
, SimpleMemberName identifier
)
614 : base (ParameterNamePrefix
+ Counter
++, identifier
.Location
)
617 Identifier
= identifier
.Value
;
620 public new static void Reset ()
626 sealed class RangeVariable
: IKnownVariable
628 public RangeVariable (QueryBlock block
, Location loc
)
634 public Block Block { get; private set; }
636 public Location Location { get; private set; }
639 List
<SimpleMemberName
> range_variables
;
641 public QueryBlock (CompilerContext ctx
, Block parent
, Location start
)
642 : base (ctx
, parent
, ParametersCompiled
.EmptyReadOnlyParameters
, start
)
646 public void AddRangeVariable (SimpleMemberName name
)
648 if (!CheckParentConflictName (this, name
.Value
, name
.Location
))
651 if (range_variables
== null)
652 range_variables
= new List
<SimpleMemberName
> ();
654 range_variables
.Add (name
);
655 AddKnownVariable (name
.Value
, new RangeVariable (this, name
.Location
));
659 // Query parameter reference can include transparent parameters
661 protected override Expression
GetParameterReferenceExpression (string name
, Location loc
)
663 Expression expr
= base.GetParameterReferenceExpression (name
, loc
);
667 TransparentParameter tp
= parameters
[0] as TransparentParameter
;
669 if (tp
.Identifier
== name
)
672 TransparentParameter tp_next
= tp
.Parent
as TransparentParameter
;
673 if (tp_next
== null && tp
.Parent
.Name
== name
)
680 expr
= new SimpleName (parameters
[0].Name
, loc
);
681 TransparentParameter tp_cursor
= (TransparentParameter
) parameters
[0];
682 while (tp_cursor
!= tp
) {
683 tp_cursor
= (TransparentParameter
) tp_cursor
.Parent
;
684 expr
= new MemberAccess (expr
, tp_cursor
.Name
);
687 return new MemberAccess (expr
, name
);
693 protected override bool HasParameterWithName (string name
)
695 return range_variables
!= null && range_variables
.Exists (l
=> l
.Value
== name
);
698 protected override void Error_AlreadyDeclared (Location loc
, string var, string reason
)
700 Report
.Error (1931, loc
, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
704 protected override void Error_AlreadyDeclared (Location loc
, string var)
706 Report
.Error (1930, loc
, "A range variable `{0}' has already been declared in this scope",
710 public override void Error_AlreadyDeclaredTypeParameter (Location loc
, string name
, string conflict
)
712 Report
.Error (1948, loc
, "A range variable `{0}' conflicts with a method type parameter",
716 public void SetParameter (Parameter parameter
)
718 base.parameters
= new ParametersCompiled (null, parameter
);
719 base.parameter_info
= new ToplevelParameterInfo
[] {
720 new ToplevelParameterInfo (this, 0)
724 public void SetParameters (Parameter first
, Parameter second
)
726 base.parameters
= new ParametersCompiled (null, first
, second
);
727 base.parameter_info
= new ToplevelParameterInfo
[] {
728 new ToplevelParameterInfo (this, 0),
729 new ToplevelParameterInfo (this, 1)