620d0e979f3f0833b23c42df3fe355b644e6b21d
[mono.git] / mcs / mcs / async.cs
1 //
2 // async.cs: Asynchronous functions
3 //
4 // Author:
5 //   Marek Safar (marek.safar@gmail.com)
6 //
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
8 //
9 // Copyright 2011 Novell, Inc.
10 //
11
12 using System;
13 using System.Collections.Generic;
14 using System.Linq;
15 #if STATIC
16 using IKVM.Reflection.Emit;
17 #else
18 using System.Reflection.Emit;
19 #endif
20
21 namespace Mono.CSharp
22 {
23         class Await : ExpressionStatement
24         {
25                 Expression expr;
26                 AwaitStatement stmt;
27
28                 public Await (Expression expr, Location loc)
29                 {
30                         this.expr = expr;
31                         this.loc = loc;
32                 }
33
34                 protected override void CloneTo (CloneContext clonectx, Expression target)
35                 {
36                         var t = (Await) target;
37
38                         t.expr = expr.Clone (clonectx);
39                 }
40
41                 public override Expression CreateExpressionTree (ResolveContext ec)
42                 {
43                         throw new NotImplementedException ("ET");
44                 }
45
46                 protected override Expression DoResolve (ResolveContext rc)
47                 {
48                         if (rc.HasSet (ResolveContext.Options.FinallyScope)) {
49                                 rc.Report.Error (1984, loc,
50                                         "The `await' operator cannot be used in the body of a finally clause");
51                         }
52
53                         if (rc.HasSet (ResolveContext.Options.CatchScope)) {
54                                 rc.Report.Error (1985, loc,
55                                         "The `await' operator cannot be used in the body of a catch clause");
56                         }
57
58                         if (rc.HasSet (ResolveContext.Options.LockScope)) {
59                                 rc.Report.Error (1996, loc,
60                                         "The `await' operator cannot be used in the body of a lock statement");
61                         }
62
63                         if (rc.HasSet (ResolveContext.Options.ExpressionTreeConversion)) {
64                                 rc.Report.Error (1989, loc, "An expression tree cannot contain an await operator");
65                                 return null;
66                         }
67
68                         if (rc.IsUnsafe) {
69                                 // TODO: New error code
70                                 rc.Report.Error (-1900, loc,
71                                         "The `await' operator cannot be used in an unsafe context");
72                         }
73
74                         var bc = (BlockContext) rc;
75
76                         if (!bc.CurrentBlock.ParametersBlock.IsAsync) {
77                                 // TODO: Should check for existence of await type but
78                                 // what to do with it
79                         }
80
81                         stmt = new AwaitStatement (expr, loc);
82                         if (!stmt.Resolve (bc))
83                                 return null;
84
85                         type = stmt.ResultType;
86                         eclass = ExprClass.Variable;
87                         return this;
88                 }
89
90                 public override void Emit (EmitContext ec)
91                 {
92                         stmt.EmitPrologue (ec);
93                         stmt.Emit (ec);
94                 }
95
96                 public void EmitAssign (EmitContext ec, FieldExpr field)
97                 {
98                         stmt.EmitPrologue (ec);
99                         field.InstanceExpression.Emit (ec);
100                         stmt.Emit (ec);
101                 }
102
103                 public override void EmitStatement (EmitContext ec)
104                 {
105                         stmt.EmitStatement (ec);
106                 }
107         }
108
109         class AwaitStatement : YieldStatement<AsyncInitializer>
110         {
111                 sealed class AwaitableMemberAccess : MemberAccess
112                 {
113                         public AwaitableMemberAccess (Expression expr)
114                                 : base (expr, "GetAwaiter")
115                         {
116                         }
117
118                         protected override void Error_TypeDoesNotContainDefinition (ResolveContext rc, TypeSpec type, string name)
119                         {
120                                 Error_WrongGetAwaiter (rc, loc, type);
121                         }
122
123                         protected override void Error_OperatorCannotBeApplied (ResolveContext rc, TypeSpec type)
124                         {
125                                 rc.Report.Error (1991, loc, "Cannot await `{0}' expression", type.GetSignatureForError ());
126                         }
127                 }
128
129                 Field awaiter;
130                 PropertyExpr is_completed;
131                 MethodSpec on_completed;
132                 MethodSpec get_result;
133                 TypeSpec type;
134
135                 public AwaitStatement (Expression expr, Location loc)
136                         : base (expr, loc)
137                 {
138                 }
139
140                 #region Properties
141
142                 public TypeSpec Type {
143                         get {
144                                 return type;
145                         }
146                 }
147
148                 public TypeSpec ResultType {
149                         get {
150                                 return get_result.ReturnType;
151                         }
152                 }
153
154                 #endregion
155
156                 protected override void DoEmit (EmitContext ec)
157                 {
158                         var fe_awaiter = new FieldExpr (awaiter, loc);
159                         fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
160
161                         //
162                         // result = awaiter.GetResult ();
163                         //
164                         var mg_result = MethodGroupExpr.CreatePredefined (get_result, fe_awaiter.Type, loc);
165                         mg_result.InstanceExpression = fe_awaiter;
166
167                         mg_result.EmitCall (ec, new Arguments (0));
168                 }
169
170                 public void EmitPrologue (EmitContext ec)
171                 {
172                         var fe_awaiter = new FieldExpr (awaiter, loc);
173                         fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
174
175                         //
176                         // awaiter = expr.GetAwaiter ();
177                         //
178                         fe_awaiter.EmitAssign (ec, expr, false, false);
179
180                         is_completed.InstanceExpression = fe_awaiter;
181                         is_completed.EmitBranchable (ec, resume_point, true);
182
183                         var mg_completed = MethodGroupExpr.CreatePredefined (on_completed, fe_awaiter.Type, loc);
184                         mg_completed.InstanceExpression = fe_awaiter;
185
186                         var args = new Arguments (1);
187                         var storey = (AsyncTaskStorey) machine_initializer.Storey;
188                         var fe_cont = new FieldExpr (storey.Continuation, loc);
189                         fe_cont.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
190
191                         args.Add (new Argument (fe_cont));
192
193                         //
194                         // awaiter.OnCompleted (continuation);
195                         //
196                         mg_completed.EmitCall (ec, args);
197
198                         base.DoEmit (ec);
199                 }
200
201                 public void EmitStatement (EmitContext ec)
202                 {
203                         EmitPrologue (ec);
204                         Emit (ec);
205
206                         if (ResultType.Kind != MemberKind.Void) {
207                                 var storey = (AsyncTaskStorey) machine_initializer.Storey;
208
209                             if (storey.HoistedReturn != null)
210                                 storey.HoistedReturn.EmitAssign (ec);
211                                 else
212                                         ec.Emit (OpCodes.Pop);
213                         }
214                 }
215
216                 static void Error_WrongGetAwaiter (ResolveContext rc, Location loc, TypeSpec type)
217                 {
218                         rc.Report.Error (1986, loc,
219                                 "The `await' operand type `{0}' must have suitable GetAwaiter method",
220                                 type.GetSignatureForError ());
221                 }
222
223                 void Error_WrongAwaiterPattern (ResolveContext rc, TypeSpec awaiter)
224                 {
225                         rc.Report.Error (1999, loc, "The awaiter type `{0}' must have suitable IsCompleted, OnCompleted, and GetResult members",
226                                 awaiter.GetSignatureForError ());
227                 }
228
229                 public override bool Resolve (BlockContext bc)
230                 {
231                         if (!base.Resolve (bc))
232                                 return false;
233
234                         type = expr.Type;
235
236                         //
237                         // The task result is of dynamic type
238                         //
239                         if (expr.Type.BuiltinType == BuiltinTypeSpec.Type.Dynamic)
240                                 throw new NotImplementedException ("dynamic await");
241
242                         //
243                         // Check whether the expression is awaitable
244                         //
245                         Expression ama = new AwaitableMemberAccess (expr).Resolve (bc);
246                         if (ama == null)
247                                 return false;
248
249                         Arguments args = new Arguments (0);
250
251                         var errors_printer = new SessionReportPrinter ();
252                         var old = bc.Report.SetPrinter (errors_printer);
253                         ama = new Invocation (ama, args).Resolve (bc);
254
255                         if (errors_printer.ErrorsCount > 0 || !MemberAccess.IsValidDotExpression (ama.Type)) {
256                                 bc.Report.SetPrinter (old);
257                                 Error_WrongGetAwaiter (bc, loc, expr.Type);
258                                 return false;
259                         }
260
261                         var awaiter_type = ama.Type;
262                         awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (awaiter_type, loc);
263                         expr = ama;
264
265                         //
266                         // Predefined: bool IsCompleted { get; } 
267                         //
268                         var is_completed_ma = new MemberAccess (expr, "IsCompleted").Resolve (bc);
269                         if (is_completed_ma != null) {
270                                 is_completed = is_completed_ma as PropertyExpr;
271                                 if (is_completed != null && is_completed.Type.BuiltinType == BuiltinTypeSpec.Type.Bool && is_completed.IsInstance && is_completed.Getter != null) {
272                                         // valid
273                                 } else {
274                                         bc.Report.SetPrinter (old);
275                                         Error_WrongAwaiterPattern (bc, awaiter_type);
276                                         return false;
277                                 }
278                         }
279
280                         bc.Report.SetPrinter (old);
281
282                         if (errors_printer.ErrorsCount > 0) {
283                                 Error_WrongAwaiterPattern (bc, awaiter_type);
284                                 return false;
285                         }
286
287                         //
288                         // Predefined: OnCompleted (Action)
289                         //
290                         if (bc.Module.PredefinedTypes.Action.Define ()) {
291                                 on_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("OnCompleted", 0,
292                                         ParametersCompiled.CreateFullyResolved (bc.Module.PredefinedTypes.Action.TypeSpec), bc.Module.Compiler.BuiltinTypes.Void),
293                                         BindingRestriction.InstanceOnly) as MethodSpec;
294
295                                 if (on_completed == null) {
296                                         Error_WrongAwaiterPattern (bc, awaiter_type);
297                                         return false;
298                                 }
299                         }
300
301                         //
302                         // Predefined: GetResult ()
303                         //
304                         // The method return type is also result type of await expression
305                         //
306                         get_result = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("GetResult", 0,
307                                 ParametersCompiled.EmptyReadOnlyParameters, null),
308                                 BindingRestriction.InstanceOnly) as MethodSpec;
309
310                         if (get_result == null) {
311                                 Error_WrongAwaiterPattern (bc, awaiter_type);
312                                 return false;
313                         }
314
315                         return true;
316                 }
317         }
318
319         public class AsyncInitializer : StateMachineInitializer
320         {
321                 TypeInferenceContext return_inference;
322
323                 public AsyncInitializer (ParametersBlock block, TypeContainer host, TypeSpec returnType)
324                         : base (block, host, returnType)
325                 {
326                 }
327
328                 #region Properties
329
330                 public override string ContainerType {
331                         get {
332                                 return "async state machine block";
333                         }
334                 }
335
336                 public override bool IsIterator {
337                         get {
338                                 return false;
339                         }
340                 }
341
342                 public Block OriginalBlock {
343                         get {
344                                 return block.Parent;
345                         }
346                 }
347
348                 public TypeInferenceContext ReturnTypeInference {
349                         get {
350                                 return return_inference;
351                         }
352                 }
353
354                 #endregion
355
356                 public static void Create (ParametersBlock block, ParametersCompiled parameters, TypeContainer host, TypeSpec returnType, Location loc)
357                 {
358                         if (returnType != null && returnType.Kind != MemberKind.Void &&
359                                 returnType != host.Module.PredefinedTypes.Task.TypeSpec &&
360                                 !returnType.IsGenericTask) {
361                                 host.Compiler.Report.Error (1983, loc, "The return type of an async method must be void, Task, or Task<T>");
362                         }
363
364                         for (int i = 0; i < parameters.Count; i++) {
365                                 Parameter p = parameters[i];
366                                 Parameter.Modifier mod = p.ModFlags;
367                                 if ((mod & Parameter.Modifier.ISBYREF) != 0) {
368                                         host.Compiler.Report.Error (1988, p.Location,
369                                                 "Async methods cannot have ref or out parameters");
370                                         return;
371                                 }
372
373                                 // TODO:
374                                 if (p is ArglistParameter) {
375                                         host.Compiler.Report.Error (1636, p.Location,
376                                                 "__arglist is not allowed in parameter list of iterators");
377                                         return;
378                                 }
379
380                                 // TODO:
381                                 if (parameters.Types[i].IsPointer) {
382                                         host.Compiler.Report.Error (1637, p.Location,
383                                                 "Iterators cannot have unsafe parameters or yield types");
384                                         return;
385                                 }
386                         }
387
388                         // TODO: Warning
389                         //if (!block.HasAwait) {
390                         //}
391
392                         block.WrapIntoAsyncTask (host, returnType);
393                 }
394
395                 protected override BlockContext CreateBlockContext (ResolveContext rc)
396                 {
397                         var ctx = base.CreateBlockContext (rc);
398                         var lambda = rc.CurrentAnonymousMethod as LambdaMethod;
399                         if (lambda != null)
400                                 return_inference = lambda.ReturnTypeInference;
401
402                         return ctx;
403                 }
404
405                 public override Expression CreateExpressionTree (ResolveContext ec)
406                 {
407                         return base.CreateExpressionTree (ec);
408                 }
409
410                 public override void Emit (EmitContext ec)
411                 {
412                         throw new NotImplementedException ();
413                 }
414
415                 protected override void EmitMoveNextEpilogue (EmitContext ec)
416                 {
417                         var storey = (AsyncTaskStorey) Storey;
418                         storey.EmitSetResult (ec);
419                 }
420
421                 public override void EmitStatement (EmitContext ec)
422                 {
423                         var storey = (AsyncTaskStorey) Storey;
424                         storey.Instance.Emit (ec);
425                         ec.Emit (OpCodes.Call, storey.StateMachineMethod.Spec);
426
427                         if (storey.Task != null) {
428                                 //
429                                 // async.$builder.Task;
430                                 //
431                                 var pe_task = new PropertyExpr (storey.Task, loc) {
432                                         InstanceExpression = new FieldExpr (storey.Builder, loc) {
433                                                 InstanceExpression = storey.Instance
434                                         },
435                                         Getter = storey.Task.Get
436                                 };
437
438                                 pe_task.Emit (ec);
439                         }
440
441                         ec.Emit (OpCodes.Ret);
442                 }
443         }
444
445         class AsyncTaskStorey : StateMachine
446         {
447                 int awaiters;
448                 Field builder, continuation;
449                 readonly TypeSpec return_type;
450                 MethodSpec set_result;
451                 PropertySpec task;
452                 LocalVariable hoisted_return;
453
454                 public AsyncTaskStorey (AsyncInitializer initializer, TypeSpec type)
455                         : base (initializer.OriginalBlock, initializer.Host, null, null, "async")
456                 {
457                         return_type = type;
458                 }
459
460                 public Field AddAwaiter (TypeSpec type, Location loc)
461                 {
462                         return AddCompilerGeneratedField ("$awaiter" + awaiters++.ToString ("X"), new TypeExpression (type, loc));
463                 }
464
465                 #region Properties
466
467                 public Field Builder {
468                         get {
469                                 return builder;
470                         }
471                 }
472
473                 public Field Continuation {
474                         get {
475                                 return continuation;
476                         }
477                 }
478
479                 public LocalVariable HoistedReturn {
480                         get {
481                                 return hoisted_return;
482                         }
483                 }
484
485                 public TypeSpec ReturnType {
486                         get {
487                                 return return_type;
488                         }
489                 }
490
491                 public PropertySpec Task {
492                         get {
493                                 return task;
494                         }
495                 }
496
497                 #endregion
498
499                 protected override bool DoDefineMembers ()
500                 {
501                         var action = Module.PredefinedTypes.Action.Resolve ();
502                         if (action != null) {
503                                 continuation = AddCompilerGeneratedField ("$continuation", new TypeExpression (action, Location));
504                                 continuation.ModFlags |= Modifiers.READONLY;
505                         }
506
507                         PredefinedType builder_type;
508                         PredefinedMember<MethodSpec> bf;
509                         PredefinedMember<MethodSpec> sr;
510                         bool has_task_return_type = false;
511                         var pred_members = Module.PredefinedMembers;
512
513                         if (return_type.Kind == MemberKind.Void) {
514                                 builder_type = Module.PredefinedTypes.AsyncVoidMethodBuilder;
515                                 bf = pred_members.AsyncVoidMethodBuilderCreate;
516                                 sr = pred_members.AsyncVoidMethodBuilderSetResult;
517                         } else if (return_type == Module.PredefinedTypes.Task.TypeSpec) {
518                                 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilder;
519                                 bf = pred_members.AsyncTaskMethodBuilderCreate;
520                                 sr = pred_members.AsyncTaskMethodBuilderSetResult;
521                                 task = pred_members.AsyncTaskMethodBuilderTask.Resolve (Location);
522                         } else {
523                                 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilderGeneric;
524                                 bf = pred_members.AsyncTaskMethodBuilderGenericCreate;
525                                 sr = pred_members.AsyncTaskMethodBuilderGenericSetResult;
526                                 task = pred_members.AsyncTaskMethodBuilderGenericTask.Resolve (Location);
527                                 has_task_return_type = true;
528                         }
529
530                         set_result = sr.Resolve (Location);
531                         var builder_factory = bf.Resolve (Location);
532                         var bt = builder_type.Resolve ();
533                         if (bt == null || set_result == null || builder_factory == null)
534                                 return false;
535
536                         //
537                         // Inflate generic Task types
538                         //
539                         if (has_task_return_type) {
540                                 bt = bt.MakeGenericType (Module, return_type.TypeArguments);
541                                 builder_factory = MemberCache.GetMember<MethodSpec> (bt, builder_factory);
542                                 set_result = MemberCache.GetMember<MethodSpec> (bt, set_result);
543
544                                 if (task != null)
545                                         task = MemberCache.GetMember<PropertySpec> (bt, task);
546                         }
547
548                         builder = AddCompilerGeneratedField ("$builder", new TypeExpression (bt, Location));
549                         builder.ModFlags |= Modifiers.READONLY;
550
551                         if (!base.DoDefineMembers ())
552                                 return false;
553
554                         MethodGroupExpr mg;
555                         var block = instance_constructors[0].Block;
556
557                         //
558                         // Initialize continuation with state machine method
559                         //
560                         if (continuation != null) {
561                                 var args = new Arguments (1);
562                                 mg = MethodGroupExpr.CreatePredefined (StateMachineMethod.Spec, spec, Location);
563                                 args.Add (new Argument (mg));
564
565                                 block.AddStatement (
566                                         new StatementExpression (new SimpleAssign (
567                                                 new FieldExpr (continuation, Location),
568                                                 new NewDelegate (action, args, Location),
569                                                 Location
570                                 )));
571                         }
572
573                         mg = MethodGroupExpr.CreatePredefined (builder_factory, bt, Location);
574                         block.AddStatement (
575                                 new StatementExpression (new SimpleAssign (
576                                 new FieldExpr (builder, Location),
577                                 new Invocation (mg, new Arguments (0)),
578                                 Location)));
579
580                         if (has_task_return_type) {
581                                 hoisted_return = LocalVariable.CreateCompilerGenerated (bt.TypeArguments[0], block, Location);
582                         }
583
584                         return true;
585                 }
586
587                 public void EmitSetResult (EmitContext ec)
588                 {
589                         //
590                         // $builder.SetResult ();
591                         // $builder.SetResult<return-type> (value);
592                         //
593                         var mg = MethodGroupExpr.CreatePredefined (set_result, set_result.DeclaringType, Location);
594                         mg.InstanceExpression = new FieldExpr (Builder, Location) {
595                                 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
596                         };
597
598                         Arguments args;
599                         if (hoisted_return == null) {
600                                 args = new Arguments (0);
601                         } else {
602                                 args = new Arguments (1);
603                                 args.Add (new Argument (new LocalVariableReference (hoisted_return, Location)));
604                         }
605
606                         mg.EmitCall (ec, args);
607                 }
608         }
609 }