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
18 // Expression should be IExpression to save some memory and make a few things
23 class QueryExpression
: AQueryClause
25 public QueryExpression (Block block
, AQueryClause query
)
26 : base (null, null, query
.Location
)
31 public override Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
)
33 return next
.BuildQueryClause (ec
, lSide
);
36 protected override Expression
DoResolve (ResolveContext ec
)
38 int counter
= QueryBlock
.TransparentParameter
.Counter
;
40 Expression e
= BuildQueryClause (ec
, null);
45 // Reset counter in probing mode to ensure that all transparent
46 // identifier anonymous types are created only once
48 if (ec
.IsInProbingMode
)
49 QueryBlock
.TransparentParameter
.Counter
= counter
;
54 protected override string MethodName
{
55 get { throw new NotSupportedException (); }
59 abstract class AQueryClause
: ShimExpression
61 class QueryExpressionAccess
: MemberAccess
63 public QueryExpressionAccess (Expression expr
, string methodName
, Location loc
)
64 : base (expr
, methodName
, loc
)
68 public QueryExpressionAccess (Expression expr
, string methodName
, TypeArguments typeArguments
, Location loc
)
69 : base (expr
, methodName
, typeArguments
, loc
)
73 protected override Expression
Error_MemberLookupFailed (ResolveContext ec
, Type container_type
, Type qualifier_type
,
74 Type queried_type
, string name
, string class_name
, MemberTypes mt
, BindingFlags bf
)
76 ec
.Report
.Error (1935, loc
, "An implementation of `{0}' query expression pattern could not be found. " +
77 "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
83 class QueryExpressionInvocation
: Invocation
, MethodGroupExpr
.IErrorHandler
85 public QueryExpressionInvocation (QueryExpressionAccess expr
, Arguments arguments
)
86 : base (expr
, arguments
)
90 protected override MethodGroupExpr
DoResolveOverload (ResolveContext ec
)
92 mg
.CustomErrorHandler
= this;
93 MethodGroupExpr rmg
= mg
.OverloadResolve (ec
, ref arguments
, false, loc
);
97 public bool AmbiguousCall (ResolveContext ec
, MethodSpec ambiguous
)
99 ec
.Report
.SymbolRelatedToPreviousError (mg
.BestCandidate
.MetaInfo
);
100 ec
.Report
.SymbolRelatedToPreviousError (ambiguous
.MetaInfo
);
101 ec
.Report
.Error (1940, loc
, "Ambiguous implementation of the query pattern `{0}' for source type `{1}'",
102 mg
.Name
, mg
.InstanceExpression
.GetSignatureForError ());
106 public bool NoExactMatch (ResolveContext ec
, MethodSpec method
)
108 var pd
= method
.Parameters
;
109 Type source_type
= pd
.ExtensionMethodType
;
110 if (source_type
!= null) {
111 Argument a
= arguments
[0];
113 if (TypeManager
.IsGenericType (source_type
) && TypeManager
.ContainsGenericParameters (source_type
)) {
114 TypeInferenceContext tic
= new TypeInferenceContext (TypeManager
.GetTypeArguments (source_type
));
115 tic
.OutputTypeInference (ec
, a
.Expr
, source_type
);
116 if (tic
.FixAllTypes (ec
)) {
117 source_type
= TypeManager
.DropGenericTypeArguments (source_type
).MakeGenericType (tic
.InferredTypeArguments
);
121 if (!Convert
.ImplicitConversionExists (ec
, a
.Expr
, source_type
)) {
122 ec
.Report
.Error (1936, loc
, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
123 mg
.Name
, TypeManager
.CSharpName (a
.Type
));
128 if (!method
.IsGenericMethod
)
131 if (mg
.Name
== "SelectMany") {
132 ec
.Report
.Error (1943, loc
,
133 "An expression type is incorrect in a subsequent `from' clause in a query expression with source type `{0}'",
134 arguments
[0].GetSignatureForError ());
136 ec
.Report
.Error (1942, loc
,
137 "An expression type in `{0}' clause is incorrect. Type inference failed in the call to `{1}'",
138 mg
.Name
.ToLower (), mg
.Name
);
146 public AQueryClause next
;
147 protected ToplevelBlock block
;
149 protected AQueryClause (ToplevelBlock block
, Expression expr
, Location loc
)
156 protected override void CloneTo (CloneContext clonectx
, Expression target
)
158 base.CloneTo (clonectx
, target
);
160 AQueryClause t
= (AQueryClause
) target
;
163 t
.block
= (ToplevelBlock
) block
.Clone (clonectx
);
166 t
.next
= (AQueryClause
) next
.Clone (clonectx
);
169 protected override Expression
DoResolve (ResolveContext ec
)
171 return expr
.Resolve (ec
);
174 public virtual Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
)
177 CreateArguments (ec
, out args
);
178 lSide
= CreateQueryExpression (lSide
, args
);
180 Select s
= next
as Select
;
181 if (s
== null || s
.IsRequired
)
182 return next
.BuildQueryClause (ec
, lSide
);
184 // Skip transparent select clause if any clause follows
185 if (next
.next
!= null)
186 return next
.next
.BuildQueryClause (ec
, lSide
);
192 protected virtual void CreateArguments (ResolveContext ec
, out Arguments args
)
194 args
= new Arguments (2);
196 LambdaExpression selector
= new LambdaExpression (loc
);
197 selector
.Block
= block
;
198 selector
.Block
.AddStatement (new ContextualReturn (expr
));
200 args
.Add (new Argument (selector
));
203 protected Invocation
CreateQueryExpression (Expression lSide
, Arguments arguments
)
205 return new QueryExpressionInvocation (
206 new QueryExpressionAccess (lSide
, MethodName
, loc
), arguments
);
209 protected Invocation
CreateQueryExpression (Expression lSide
, TypeArguments typeArguments
, Arguments arguments
)
211 return new QueryExpressionInvocation (
212 new QueryExpressionAccess (lSide
, MethodName
, typeArguments
, loc
), arguments
);
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 abstract class ARangeVariableQueryClause
: AQueryClause
235 sealed class RangeAnonymousTypeParameter
: AnonymousTypeParameter
237 public RangeAnonymousTypeParameter (Expression initializer
, SimpleMemberName parameter
)
238 : base (initializer
, parameter
.Value
, parameter
.Location
)
242 protected override void Error_InvalidInitializer (ResolveContext ec
, string initializer
)
244 ec
.Report
.Error (1932, loc
, "A range variable `{0}' cannot be initialized with `{1}'",
249 protected ARangeVariableQueryClause (ToplevelBlock block
, Expression expr
)
250 : base (block
, expr
, expr
.Location
)
254 protected static Expression
CreateRangeVariableType (ToplevelBlock block
, IMemberContext context
, SimpleMemberName name
, Expression init
)
256 var args
= new List
<AnonymousTypeParameter
> (2);
257 args
.Add (new AnonymousTypeParameter (block
.Parameters
[0]));
258 args
.Add (new RangeAnonymousTypeParameter (init
, name
));
259 return new NewAnonymousType (args
, context
.CurrentTypeDefinition
, name
.Location
);
263 class QueryStartClause
: AQueryClause
265 public QueryStartClause (Expression expr
)
266 : base (null, expr
, expr
.Location
)
270 public override Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
)
272 expr
= expr
.Resolve (ec
);
276 if (TypeManager
.IsDynamicType (expr
.Type
) || expr
.Type
== TypeManager
.void_type
) {
277 ec
.Report
.Error (1979, expr
.Location
,
278 "Query expression with a source or join sequence of type `{0}' is not allowed",
279 TypeManager
.CSharpName (expr
.Type
));
283 return next
.BuildQueryClause (ec
, expr
);
286 protected override Expression
DoResolve (ResolveContext ec
)
288 Expression e
= BuildQueryClause (ec
, null);
289 return e
.Resolve (ec
);
292 protected override string MethodName
{
293 get { throw new NotSupportedException (); }
297 class Cast
: QueryStartClause
299 // We don't have to clone cast type
300 readonly FullNamedExpression type_expr
;
302 public Cast (FullNamedExpression type
, Expression expr
)
305 this.type_expr
= type
;
308 public override Expression
BuildQueryClause (ResolveContext ec
, Expression lSide
)
310 lSide
= CreateQueryExpression (expr
, new TypeArguments (type_expr
), null);
312 return next
.BuildQueryClause (ec
, lSide
);
317 protected override string MethodName
{
318 get { return "Cast"; }
322 class GroupBy
: AQueryClause
324 Expression element_selector
;
325 ToplevelBlock element_block
;
327 public GroupBy (ToplevelBlock block
, Expression elementSelector
, ToplevelBlock elementBlock
, Expression keySelector
, Location loc
)
328 : base (block
, keySelector
, loc
)
331 // Optimizes clauses like `group A by A'
333 if (!elementSelector
.Equals (keySelector
)) {
334 this.element_selector
= elementSelector
;
335 this.element_block
= elementBlock
;
339 protected override void CreateArguments (ResolveContext ec
, out Arguments args
)
341 base.CreateArguments (ec
, out args
);
343 if (element_selector
!= null) {
344 LambdaExpression lambda
= new LambdaExpression (element_selector
.Location
);
345 lambda
.Block
= element_block
;
346 lambda
.Block
.AddStatement (new ContextualReturn (element_selector
));
347 args
.Add (new Argument (lambda
));
351 protected override void CloneTo (CloneContext clonectx
, Expression target
)
353 GroupBy t
= (GroupBy
) target
;
354 if (element_selector
!= null) {
355 t
.element_selector
= element_selector
.Clone (clonectx
);
356 t
.element_block
= (ToplevelBlock
) element_block
.Clone (clonectx
);
359 base.CloneTo (clonectx
, t
);
362 protected override string MethodName
{
363 get { return "GroupBy"; }
367 class Join
: ARangeVariableQueryClause
369 readonly SimpleMemberName lt
;
370 ToplevelBlock inner_selector
, outer_selector
;
372 public Join (ToplevelBlock block
, SimpleMemberName lt
, Expression inner
, ToplevelBlock outerSelector
, ToplevelBlock innerSelector
, Location loc
)
373 : base (block
, inner
)
376 this.outer_selector
= outerSelector
;
377 this.inner_selector
= innerSelector
;
380 protected override void CreateArguments (ResolveContext ec
, out Arguments args
)
382 args
= new Arguments (4);
384 args
.Add (new Argument (expr
));
386 LambdaExpression lambda
= new LambdaExpression (outer_selector
.StartLocation
);
387 lambda
.Block
= outer_selector
;
388 args
.Add (new Argument (lambda
));
390 lambda
= new LambdaExpression (inner_selector
.StartLocation
);
391 lambda
.Block
= inner_selector
;
392 args
.Add (new Argument (lambda
));
394 Expression result_selector_expr
;
395 SimpleMemberName into_variable
= GetIntoVariable ();
397 // When select follows use is as result selector
399 if (next
is Select
) {
400 result_selector_expr
= next
.Expr
;
403 result_selector_expr
= CreateRangeVariableType (block
, ec
.MemberContext
, into_variable
,
404 new SimpleName (into_variable
.Value
, into_variable
.Location
));
407 LambdaExpression result_selector
= new LambdaExpression (lt
.Location
);
408 result_selector
.Block
= new QueryBlock (ec
.Compiler
, block
.Parent
, block
.Parameters
, into_variable
, block
.StartLocation
);
409 result_selector
.Block
.AddStatement (new ContextualReturn (result_selector_expr
));
411 args
.Add (new Argument (result_selector
));
414 protected virtual SimpleMemberName
GetIntoVariable ()
419 protected override void CloneTo (CloneContext clonectx
, Expression target
)
421 Join t
= (Join
) target
;
422 t
.inner_selector
= (ToplevelBlock
) inner_selector
.Clone (clonectx
);
423 t
.outer_selector
= (ToplevelBlock
) outer_selector
.Clone (clonectx
);
424 base.CloneTo (clonectx
, t
);
427 protected override string MethodName
{
428 get { return "Join"; }
432 class GroupJoin
: Join
434 readonly SimpleMemberName
into;
436 public GroupJoin (ToplevelBlock block
, SimpleMemberName lt
, Expression inner
,
437 ToplevelBlock outerSelector
, ToplevelBlock innerSelector
, SimpleMemberName
into, Location loc
)
438 : base (block
, lt
, inner
, outerSelector
, innerSelector
, loc
)
443 protected override SimpleMemberName
GetIntoVariable ()
448 protected override string MethodName
{
449 get { return "GroupJoin"; }
453 class Let
: ARangeVariableQueryClause
455 public Let (ToplevelBlock block
, TypeContainer container
, SimpleMemberName identifier
, Expression expr
)
456 : base (block
, CreateRangeVariableType (block
, container
, identifier
, expr
))
460 protected override string MethodName
{
461 get { return "Select"; }
465 class Select
: AQueryClause
467 public Select (ToplevelBlock block
, Expression expr
, Location loc
)
468 : base (block
, expr
, loc
)
473 // For queries like `from a orderby a select a'
474 // the projection is transparent and select clause can be safely removed
476 public bool IsRequired
{
478 SimpleName sn
= expr
as SimpleName
;
482 return sn
.Name
!= block
.Parameters
.FixedParameters
[0].Name
;
486 protected override string MethodName
{
487 get { return "Select"; }
491 class SelectMany
: ARangeVariableQueryClause
495 public SelectMany (ToplevelBlock block
, SimpleMemberName lt
, Expression expr
)
501 protected override void CreateArguments (ResolveContext ec
, out Arguments args
)
503 base.CreateArguments (ec
, out args
);
505 Expression result_selector_expr
;
507 // When select follow use is as result selector
509 if (next
is Select
) {
510 result_selector_expr
= next
.Expr
;
513 result_selector_expr
= CreateRangeVariableType (block
, ec
.MemberContext
, lt
, new SimpleName (lt
.Value
, lt
.Location
));
516 LambdaExpression result_selector
= new LambdaExpression (lt
.Location
);
517 result_selector
.Block
= new QueryBlock (ec
.Compiler
, block
.Parent
, block
.Parameters
, lt
, block
.StartLocation
);
518 result_selector
.Block
.AddStatement (new ContextualReturn (result_selector_expr
));
520 args
.Add (new Argument (result_selector
));
523 protected override string MethodName
{
524 get { return "SelectMany"; }
528 class Where
: AQueryClause
530 public Where (ToplevelBlock block
, BooleanExpression expr
, Location loc
)
531 : base (block
, expr
, loc
)
535 protected override string MethodName
{
536 get { return "Where"; }
540 class OrderByAscending
: AQueryClause
542 public OrderByAscending (ToplevelBlock block
,Expression expr
)
543 : base (block
, expr
, expr
.Location
)
547 protected override string MethodName
{
548 get { return "OrderBy"; }
552 class OrderByDescending
: AQueryClause
554 public OrderByDescending (ToplevelBlock block
, Expression expr
)
555 : base (block
, expr
, expr
.Location
)
559 protected override string MethodName
{
560 get { return "OrderByDescending"; }
564 class ThenByAscending
: OrderByAscending
566 public ThenByAscending (ToplevelBlock block
, Expression expr
)
571 protected override string MethodName
{
572 get { return "ThenBy"; }
576 class ThenByDescending
: OrderByDescending
578 public ThenByDescending (ToplevelBlock block
, Expression expr
)
583 protected override string MethodName
{
584 get { return "ThenByDescending"; }
589 // Implicit query block
591 class QueryBlock
: ToplevelBlock
594 // Transparent parameters are used to package up the intermediate results
595 // and pass them onto next clause
597 public sealed class TransparentParameter
: ImplicitLambdaParameter
599 public static int Counter
;
600 const string ParameterNamePrefix
= "<>__TranspIdent";
602 public readonly ParametersCompiled Parent
;
603 public readonly string Identifier
;
605 public TransparentParameter (ParametersCompiled parent
, SimpleMemberName identifier
)
606 : base (ParameterNamePrefix
+ Counter
++, identifier
.Location
)
609 Identifier
= identifier
.Value
;
612 public static void Reset ()
618 public sealed class ImplicitQueryParameter
: ImplicitLambdaParameter
620 public ImplicitQueryParameter (string name
, Location loc
)
626 public QueryBlock (CompilerContext ctx
, Block parent
, SimpleMemberName lt
, Location start
)
627 : base (ctx
, parent
, new ParametersCompiled (ctx
, new ImplicitQueryParameter (lt
.Value
, lt
.Location
)), start
)
630 base.CheckParentConflictName (parent
.Toplevel
, lt
.Value
, lt
.Location
);
633 public QueryBlock (CompilerContext ctx
, Block parent
, ParametersCompiled parameters
, SimpleMemberName lt
, Location start
)
634 : base (ctx
, parent
, new ParametersCompiled (ctx
, parameters
[0].Clone (), new ImplicitQueryParameter (lt
.Value
, lt
.Location
)), start
)
638 public QueryBlock (CompilerContext ctx
, Block parent
, Location start
)
639 : base (ctx
, parent
, parent
.Toplevel
.Parameters
.Clone (), start
)
643 public void AddTransparentParameter (CompilerContext ctx
, SimpleMemberName name
)
645 base.CheckParentConflictName (this, name
.Value
, name
.Location
);
647 parameters
= new ParametersCompiled (ctx
, new TransparentParameter (parameters
, name
));
650 protected override bool CheckParentConflictName (ToplevelBlock block
, string name
, Location l
)
656 // Query parameter reference can include transparent parameters
658 protected override Expression
GetParameterReferenceExpression (string name
, Location loc
)
660 Expression expr
= base.GetParameterReferenceExpression (name
, loc
);
664 TransparentParameter tp
= parameters
[0] as TransparentParameter
;
666 if (tp
.Identifier
== name
)
669 TransparentParameter tp_next
= tp
.Parent
[0] as TransparentParameter
;
670 if (tp_next
== null) {
671 if (tp
.Parent
.GetParameterIndexByName (name
) >= 0)
679 expr
= new SimpleName (parameters
[0].Name
, loc
);
680 TransparentParameter tp_cursor
= (TransparentParameter
) parameters
[0];
681 while (tp_cursor
!= tp
) {
682 tp_cursor
= (TransparentParameter
) tp_cursor
.Parent
[0];
683 expr
= new MemberAccess (expr
, tp_cursor
.Name
);
686 return new MemberAccess (expr
, name
);
692 protected override void Error_AlreadyDeclared (Location loc
, string var, string reason
)
694 Report
.Error (1931, loc
, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
698 protected override void Error_AlreadyDeclared (Location loc
, string var)
700 Report
.Error (1930, loc
, "A range variable `{0}' has already been declared in this scope",
704 public override void Error_AlreadyDeclaredTypeParameter (Report r
, Location loc
, string name
, string conflict
)
706 r
.Error (1948, loc
, "A range variable `{0}' conflicts with a method type parameter",