forked from auduchinok/fsharp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathErrorLogger.fs
More file actions
executable file
·655 lines (547 loc) · 25.9 KB
/
Copy pathErrorLogger.fs
File metadata and controls
executable file
·655 lines (547 loc) · 25.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
module public Microsoft.FSharp.Compiler.ErrorLogger
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Range
open System
//------------------------------------------------------------------------
// General error recovery mechanism
//-----------------------------------------------------------------------
/// Represents the style being used to format errors
[<RequireQualifiedAccess>]
type ErrorStyle =
| DefaultErrors
| EmacsErrors
| TestErrors
| VSErrors
| GccErrors
/// Thrown when we want to add some range information to a .NET exception
exception WrappedError of exn * range with
override this.Message =
match this :> exn with
| WrappedError (exn, _) -> "WrappedError(" + exn.Message + ")"
| _ -> "WrappedError"
/// Thrown when immediate, local error recovery is not possible. This indicates
/// we've reported an error but need to make a non-local transfer of control.
/// Error recovery may catch this and continue (see 'errorRecovery')
///
/// The exception that caused the report is carried as data because in some
/// situations (LazyWithContext) we may need to re-report the original error
/// when a lazy thunk is re-evaluated.
exception ReportedError of exn option with
override this.Message =
let msg = "The exception has been reported. This internal exception should now be caught at an error recovery point on the stack."
match this :> exn with
| ReportedError (Some exn) -> msg + " Original message: " + exn.Message + ")"
| _ -> msg
let rec findOriginalException err =
match err with
| ReportedError (Some err) -> err
| WrappedError(err, _) -> findOriginalException err
| _ -> err
type Suggestions = unit -> Collections.Generic.HashSet<string>
let NoSuggestions : Suggestions = fun () -> Collections.Generic.HashSet()
/// Thrown when we stop processing the F# Interactive entry or #load.
exception StopProcessingExn of exn option with
override this.Message = "Processing of a script fragment has stopped because an exception has been raised"
override this.ToString() =
match this :> exn with
| StopProcessingExn(Some exn) -> "StopProcessingExn, originally (" + exn.ToString() + ")"
| _ -> "StopProcessingExn"
let (|StopProcessing|_|) exn = match exn with StopProcessingExn _ -> Some () | _ -> None
let StopProcessing<'T> = StopProcessingExn None
exception NumberedError of (int * string) * range with // int is e.g. 191 in FS0191
override this.Message =
match this :> exn with
| NumberedError((_, msg), _) -> msg
| _ -> "impossible"
exception Error of (int * string) * range with // int is e.g. 191 in FS0191 // eventually remove this type, it is a transitional artifact of the old unnumbered error style
override this.Message =
match this :> exn with
| Error((_, msg), _) -> msg
| _ -> "impossible"
exception InternalError of msg: string * range with
override this.Message =
match this :> exn with
| InternalError(msg, m) -> msg + m.ToString()
| _ -> "impossible"
exception UserCompilerMessage of string * int * range
exception LibraryUseOnly of range
exception Deprecated of string * range
exception Experimental of string * range
exception PossibleUnverifiableCode of range
exception UnresolvedReferenceNoRange of (*assemblyname*) string
exception UnresolvedReferenceError of (*assemblyname*) string * range
exception UnresolvedPathReferenceNoRange of (*assemblyname*) string * (*path*) string
exception UnresolvedPathReference of (*assemblyname*) string * (*path*) string * range
exception ErrorWithSuggestions of (int * string) * range * string * Suggestions with // int is e.g. 191 in FS0191
override this.Message =
match this :> exn with
| ErrorWithSuggestions((_, msg), _, _, _) -> msg
| _ -> "impossible"
let inline protectAssemblyExploration dflt f =
try
f()
with
| UnresolvedPathReferenceNoRange _ -> dflt
| _ -> reraise()
let inline protectAssemblyExplorationF dflt f =
try
f()
with
| UnresolvedPathReferenceNoRange (asmName, path) -> dflt(asmName, path)
| _ -> reraise()
let inline protectAssemblyExplorationNoReraise dflt1 dflt2 f =
try
f()
with
| UnresolvedPathReferenceNoRange _ -> dflt1
| _ -> dflt2
// Attach a range if this is a range dual exception.
let rec AttachRange m (exn:exn) =
if m = range0 then exn
else
match exn with
// Strip TargetInvocationException wrappers
| :? System.Reflection.TargetInvocationException -> AttachRange m exn.InnerException
| UnresolvedReferenceNoRange(a) -> UnresolvedReferenceError(a, m)
| UnresolvedPathReferenceNoRange(a, p) -> UnresolvedPathReference(a, p, m)
| Failure(msg) -> InternalError(msg^" (Failure)", m)
| :? System.ArgumentException as exn -> InternalError(exn.Message + " (ArgumentException)", m)
| notARangeDual -> notARangeDual
//----------------------------------------------------------------------------
// Error logger interface
type Exiter =
abstract Exit : int -> 'T
let QuitProcessExiter =
{ new Exiter with
member __.Exit(n) =
try
System.Environment.Exit(n)
with _ ->
()
FSComp.SR.elSysEnvExitDidntExit()
|> failwith }
/// Closed enumeration of build phases.
[<RequireQualifiedAccess>]
type BuildPhase =
| DefaultPhase
| Compile
| Parameter | Parse | TypeCheck
| CodeGen
| Optimize | IlxGen | IlGen | Output
| Interactive // An error seen during interactive execution
/// Literal build phase subcategory strings.
module BuildPhaseSubcategory =
[<Literal>]
let DefaultPhase = ""
[<Literal>]
let Compile = "compile"
[<Literal>]
let Parameter = "parameter"
[<Literal>]
let Parse = "parse"
[<Literal>]
let TypeCheck = "typecheck"
[<Literal>]
let CodeGen = "codegen"
[<Literal>]
let Optimize = "optimize"
[<Literal>]
let IlxGen = "ilxgen"
[<Literal>]
let IlGen = "ilgen"
[<Literal>]
let Output = "output"
[<Literal>]
let Interactive = "interactive"
[<Literal>]
let Internal = "internal" // Compiler ICE
[<System.Diagnostics.DebuggerDisplay("{DebugDisplay()}")>]
type PhasedDiagnostic =
{ Exception:exn; Phase:BuildPhase }
/// Construct a phased error
static member Create(exn:exn, phase:BuildPhase) : PhasedDiagnostic =
// FUTURE: renable this assert, which has historically triggered in some compiler service scenarios
// System.Diagnostics.Debug.Assert(phase<>BuildPhase.DefaultPhase, sprintf "Compile error seen with no phase to attribute it to.%A %s %s" phase exn.Message exn.StackTrace )
{Exception = exn; Phase=phase}
member this.DebugDisplay() =
sprintf "%s: %s" (this.Subcategory()) this.Exception.Message
/// This is the textual subcategory to display in error and warning messages (shows only under --vserrors):
///
/// file1.fs(72): subcategory warning FS0072: This is a warning message
///
member pe.Subcategory() =
match pe.Phase with
| BuildPhase.DefaultPhase -> BuildPhaseSubcategory.DefaultPhase
| BuildPhase.Compile -> BuildPhaseSubcategory.Compile
| BuildPhase.Parameter -> BuildPhaseSubcategory.Parameter
| BuildPhase.Parse -> BuildPhaseSubcategory.Parse
| BuildPhase.TypeCheck -> BuildPhaseSubcategory.TypeCheck
| BuildPhase.CodeGen -> BuildPhaseSubcategory.CodeGen
| BuildPhase.Optimize -> BuildPhaseSubcategory.Optimize
| BuildPhase.IlxGen -> BuildPhaseSubcategory.IlxGen
| BuildPhase.IlGen -> BuildPhaseSubcategory.IlGen
| BuildPhase.Output -> BuildPhaseSubcategory.Output
| BuildPhase.Interactive -> BuildPhaseSubcategory.Interactive
/// Return true if the textual phase given is from the compile part of the build process.
/// This set needs to be equal to the set of subcategories that the language service can produce.
static member IsSubcategoryOfCompile(subcategory:string) =
// This code logic is duplicated in DocumentTask.cs in the language service.
match subcategory with
| BuildPhaseSubcategory.Compile
| BuildPhaseSubcategory.Parameter
| BuildPhaseSubcategory.Parse
| BuildPhaseSubcategory.TypeCheck -> true
| null
| BuildPhaseSubcategory.DefaultPhase
| BuildPhaseSubcategory.CodeGen
| BuildPhaseSubcategory.Optimize
| BuildPhaseSubcategory.IlxGen
| BuildPhaseSubcategory.IlGen
| BuildPhaseSubcategory.Output
| BuildPhaseSubcategory.Interactive -> false
| BuildPhaseSubcategory.Internal
// Getting here means the compiler has ICE-d. Let's not pile on by showing the unknownSubcategory assert below.
// Just treat as an unknown-to-LanguageService error.
-> false
| unknownSubcategory ->
System.Diagnostics.Debug.Assert(false, sprintf "Subcategory '%s' could not be correlated with a build phase." unknownSubcategory)
// Recovery is to treat this as a 'build' error. Downstream, the project system and language service will treat this as
// if it came from the build and not the language service.
false
/// Return true if this phase is one that's known to be part of the 'compile'. This is the initial phase of the entire compilation that
/// the language service knows about.
member pe.IsPhaseInCompile() =
let isPhaseInCompile =
match pe.Phase with
| BuildPhase.Compile | BuildPhase.Parameter | BuildPhase.Parse | BuildPhase.TypeCheck -> true
| _ -> false
// Sanity check ensures that Phase matches Subcategory
#if DEBUG
if isPhaseInCompile then
System.Diagnostics.Debug.Assert(PhasedDiagnostic.IsSubcategoryOfCompile(pe.Subcategory()), "Subcategory did not match isPhaseInCompile=true")
else
System.Diagnostics.Debug.Assert(not(PhasedDiagnostic.IsSubcategoryOfCompile(pe.Subcategory())), "Subcategory did not match isPhaseInCompile=false")
#endif
isPhaseInCompile
[<AbstractClass>]
[<System.Diagnostics.DebuggerDisplay("{DebugDisplay()}")>]
type ErrorLogger(nameForDebugging:string) =
abstract ErrorCount: int
// The 'Impl' factoring enables a developer to place a breakpoint at the non-Impl
// code just below and get a breakpoint for all error logger implementations.
abstract DiagnosticSink: phasedError: PhasedDiagnostic * isError: bool -> unit
member __.DebugDisplay() = sprintf "ErrorLogger(%s)" nameForDebugging
let DiscardErrorsLogger =
{ new ErrorLogger("DiscardErrorsLogger") with
member x.DiagnosticSink(phasedError, isError) = ()
member x.ErrorCount = 0 }
let AssertFalseErrorLogger =
{ new ErrorLogger("AssertFalseErrorLogger") with
// TODO: renable these asserts in the compiler service
member x.DiagnosticSink(phasedError, isError) = (* assert false; *) ()
member x.ErrorCount = (* assert false; *) 0
}
type CapturingErrorLogger(nm) =
inherit ErrorLogger(nm)
let mutable errorCount = 0
let diagnostics = ResizeArray()
override x.DiagnosticSink(phasedError, isError) =
if isError then errorCount <- errorCount + 1
diagnostics.Add (phasedError, isError)
override x.ErrorCount = errorCount
member x.Diagnostics = diagnostics |> Seq.toList
member x.CommitDelayedDiagnostics(errorLogger:ErrorLogger) =
// Eagerly grab all the errors and warnings from the mutable collection
let errors = diagnostics.ToArray()
errors |> Array.iter errorLogger.DiagnosticSink
/// Type holds thread-static globals for use by the compile.
type internal CompileThreadStatic =
[<System.ThreadStatic;DefaultValue>]
static val mutable private buildPhase : BuildPhase
[<System.ThreadStatic;DefaultValue>]
static val mutable private errorLogger : ErrorLogger
static member BuildPhaseUnchecked with get() = CompileThreadStatic.buildPhase (* This can be a null value *)
static member BuildPhase
with get() =
match box CompileThreadStatic.buildPhase with
// FUTURE: renable these asserts, which have historically fired in some compiler service scernaios
| null -> (* assert false; *) BuildPhase.DefaultPhase
| _ -> CompileThreadStatic.buildPhase
and set v = CompileThreadStatic.buildPhase <- v
static member ErrorLogger
with get() =
match box CompileThreadStatic.errorLogger with
| null -> AssertFalseErrorLogger
| _ -> CompileThreadStatic.errorLogger
and set v = CompileThreadStatic.errorLogger <- v
[<AutoOpen>]
module ErrorLoggerExtensions =
open System.Reflection
/// Instruct the exception not to reset itself when thrown again.
let PreserveStackTrace(exn) =
try
let preserveStackTrace = typeof<System.Exception>.GetMethod("InternalPreserveStackTrace", BindingFlags.Instance ||| BindingFlags.NonPublic)
preserveStackTrace.Invoke(exn, null) |> ignore
with _ ->
// This is probably only the mono case.
System.Diagnostics.Debug.Assert(false, "Could not preserve stack trace for watson exception.")
()
/// Reraise an exception if it is one we want to report to Watson.
let ReraiseIfWatsonable(exn:exn) =
#if FX_REDUCED_EXCEPTIONS
ignore exn
()
#else
match exn with
// These few SystemExceptions which we don't report to Watson are because we handle these in some way in Build.fs
| :? System.Reflection.TargetInvocationException -> ()
| :? System.NotSupportedException -> ()
| :? System.IO.IOException -> () // This covers FileNotFoundException and DirectoryNotFoundException
| :? System.UnauthorizedAccessException -> ()
| Failure _ // This gives reports for compiler INTERNAL ERRORs
| :? System.SystemException ->
PreserveStackTrace(exn)
raise exn
| _ -> ()
#endif
type ErrorLogger with
member x.ErrorR exn =
match exn with
| InternalError (s, _)
| Failure s as exn -> System.Diagnostics.Debug.Assert(false, sprintf "Unexpected exception raised in compiler: %s\n%s" s (exn.ToString()))
| _ -> ()
match exn with
| StopProcessing
| ReportedError _ ->
PreserveStackTrace(exn)
raise exn
| _ -> x.DiagnosticSink(PhasedDiagnostic.Create(exn, CompileThreadStatic.BuildPhase), true)
member x.Warning exn =
match exn with
| StopProcessing
| ReportedError _ ->
PreserveStackTrace(exn)
raise exn
| _ -> x.DiagnosticSink(PhasedDiagnostic.Create(exn, CompileThreadStatic.BuildPhase), false)
member x.Error exn =
x.ErrorR exn
raise (ReportedError (Some exn))
member x.SimulateError (ph:PhasedDiagnostic) =
x.DiagnosticSink (ph, true)
raise (ReportedError (Some ph.Exception))
member x.ErrorRecovery (exn:exn) (m:range) =
// Never throws ReportedError.
// Throws StopProcessing and exceptions raised by the DiagnosticSink(exn) handler.
match exn with
(* Don't send ThreadAbortException down the error channel *)
#if FX_REDUCED_EXCEPTIONS
#else
| :? System.Threading.ThreadAbortException | WrappedError((:? System.Threading.ThreadAbortException), _) -> ()
#endif
| ReportedError _ | WrappedError(ReportedError _, _) -> ()
| StopProcessing | WrappedError(StopProcessing, _) ->
PreserveStackTrace(exn)
raise exn
| _ ->
try
x.ErrorR (AttachRange m exn) // may raise exceptions, e.g. an fsi error sink raises StopProcessing.
ReraiseIfWatsonable(exn)
with
| ReportedError _ | WrappedError(ReportedError _, _) -> ()
member x.StopProcessingRecovery (exn:exn) (m:range) =
// Do standard error recovery.
// Additionally ignore/catch StopProcessing. [This is the only catch handler for StopProcessing].
// Additionally ignore/catch ReportedError.
// Can throw other exceptions raised by the DiagnosticSink(exn) handler.
match exn with
| StopProcessing | WrappedError(StopProcessing, _) -> () // suppress, so skip error recovery.
| _ ->
try
x.ErrorRecovery exn m
with
| StopProcessing | WrappedError(StopProcessing, _) -> () // catch, e.g. raised by DiagnosticSink.
| ReportedError _ | WrappedError(ReportedError _, _) -> () // catch, but not expected unless ErrorRecovery is changed.
member x.ErrorRecoveryNoRange (exn:exn) =
x.ErrorRecovery exn range0
/// NOTE: The change will be undone when the returned "unwind" object disposes
let PushThreadBuildPhaseUntilUnwind (phase:BuildPhase) =
let oldBuildPhase = CompileThreadStatic.BuildPhaseUnchecked
CompileThreadStatic.BuildPhase <- phase
{ new System.IDisposable with
member x.Dispose() = CompileThreadStatic.BuildPhase <- oldBuildPhase (* maybe null *) }
/// NOTE: The change will be undone when the returned "unwind" object disposes
let PushErrorLoggerPhaseUntilUnwind(errorLoggerTransformer : ErrorLogger -> #ErrorLogger) =
let oldErrorLogger = CompileThreadStatic.ErrorLogger
let newErrorLogger = errorLoggerTransformer oldErrorLogger
let newInstalled = ref true
let newIsInstalled() = if !newInstalled then () else (assert false; (); (*failwith "error logger used after unwind"*)) // REVIEW: ok to throw?
let chkErrorLogger = { new ErrorLogger("PushErrorLoggerPhaseUntilUnwind") with
member __.DiagnosticSink(phasedError, isError) = newIsInstalled(); newErrorLogger.DiagnosticSink(phasedError, isError)
member __.ErrorCount = newIsInstalled(); newErrorLogger.ErrorCount }
CompileThreadStatic.ErrorLogger <- chkErrorLogger
{ new System.IDisposable with
member __.Dispose() =
CompileThreadStatic.ErrorLogger <- oldErrorLogger
newInstalled := false }
let SetThreadBuildPhaseNoUnwind(phase:BuildPhase) = CompileThreadStatic.BuildPhase <- phase
let SetThreadErrorLoggerNoUnwind(errorLogger) = CompileThreadStatic.ErrorLogger <- errorLogger
// Global functions are still used by parser and TAST ops.
/// Raises an exception with error recovery and returns unit.
let errorR exn = CompileThreadStatic.ErrorLogger.ErrorR exn
/// Raises a warning with error recovery and returns unit.
let warning exn = CompileThreadStatic.ErrorLogger.Warning exn
/// Raises a special exception and returns 'T - can be caught later at an errorRecovery point.
let error exn = CompileThreadStatic.ErrorLogger.Error exn
/// Simulates an error. For test purposes only.
let simulateError (p : PhasedDiagnostic) = CompileThreadStatic.ErrorLogger.SimulateError p
let diagnosticSink (phasedError, isError) = CompileThreadStatic.ErrorLogger.DiagnosticSink (phasedError, isError)
let errorSink pe = diagnosticSink (pe, true)
let warnSink pe = diagnosticSink (pe, false)
let errorRecovery exn m = CompileThreadStatic.ErrorLogger.ErrorRecovery exn m
let stopProcessingRecovery exn m = CompileThreadStatic.ErrorLogger.StopProcessingRecovery exn m
let errorRecoveryNoRange exn = CompileThreadStatic.ErrorLogger.ErrorRecoveryNoRange exn
let report f =
f()
let deprecatedWithError s m = errorR(Deprecated(s, m))
// Note: global state, but only for compiling FSharp.Core.dll
let mutable reportLibraryOnlyFeatures = true
let libraryOnlyError m = if reportLibraryOnlyFeatures then errorR(LibraryUseOnly(m))
let libraryOnlyWarning m = if reportLibraryOnlyFeatures then warning(LibraryUseOnly(m))
let deprecatedOperator m = deprecatedWithError (FSComp.SR.elDeprecatedOperator()) m
let mlCompatWarning s m = warning(UserCompilerMessage(FSComp.SR.mlCompatMessage s, 62, m))
let suppressErrorReporting f =
let errorLogger = CompileThreadStatic.ErrorLogger
try
let errorLogger =
{ new ErrorLogger("suppressErrorReporting") with
member __.DiagnosticSink(_phasedError, _isError) = ()
member __.ErrorCount = 0 }
SetThreadErrorLoggerNoUnwind(errorLogger)
f()
finally
SetThreadErrorLoggerNoUnwind(errorLogger)
let conditionallySuppressErrorReporting cond f = if cond then suppressErrorReporting f else f()
//------------------------------------------------------------------------
// Errors as data: Sometimes we have to reify errors as data, e.g. if backtracking
//
// REVIEW: consider using F# computation expressions here
[<NoEquality; NoComparison>]
type OperationResult<'T> =
| OkResult of (* warnings: *) exn list * 'T
| ErrorResult of (* warnings: *) exn list * exn
type ImperativeOperationResult = OperationResult<unit>
let ReportWarnings warns =
match warns with
| [] -> () // shortcut in common case
| _ -> List.iter warning warns
let CommitOperationResult res =
match res with
| OkResult (warns, res) -> ReportWarnings warns; res
| ErrorResult (warns, err) -> ReportWarnings warns; error err
let RaiseOperationResult res : unit = CommitOperationResult res
let ErrorD err = ErrorResult([], err)
let WarnD err = OkResult([err], ())
let CompleteD = OkResult([], ())
let ResultD x = OkResult([], x)
let CheckNoErrorsAndGetWarnings res =
match res with
| OkResult (warns, _) -> Some warns
| ErrorResult _ -> None
/// The bind in the monad. Stop on first error. Accumulate warnings and continue.
let (++) res f =
match res with
| OkResult([], res) -> (* tailcall *) f res
| OkResult(warns, res) ->
match f res with
| OkResult(warns2, res2) -> OkResult(warns@warns2, res2)
| ErrorResult(warns2, err) -> ErrorResult(warns@warns2, err)
| ErrorResult(warns, err) ->
ErrorResult(warns, err)
/// Stop on first error. Accumulate warnings and continue.
let rec IterateD f xs =
match xs with
| [] -> CompleteD
| h :: t -> f h ++ (fun () -> IterateD f t)
let rec WhileD gd body = if gd() then body() ++ (fun () -> WhileD gd body) else CompleteD
let MapD f xs =
let rec loop acc xs =
match xs with
| [] -> ResultD (List.rev acc)
| h :: t -> f h ++ (fun x -> loop (x::acc) t)
loop [] xs
type TrackErrorsBuilder() =
member x.Bind(res, k) = res ++ k
member x.Return res = ResultD res
member x.ReturnFrom res = res
member x.For(seq, k) = IterateD k seq
member x.While(gd, k) = WhileD gd k
member x.Zero() = CompleteD
let trackErrors = TrackErrorsBuilder()
/// Stop on first error. Accumulate warnings and continue.
let OptionD f xs =
match xs with
| None -> CompleteD
| Some h -> f h
/// Stop on first error. Report index
let IterateIdxD f xs =
let rec loop xs i = match xs with [] -> CompleteD | h :: t -> f i h ++ (fun () -> loop t (i+1))
loop xs 0
/// Stop on first error. Accumulate warnings and continue.
let rec Iterate2D f xs ys =
match xs, ys with
| [], [] -> CompleteD
| h1 :: t1, h2::t2 -> f h1 h2 ++ (fun () -> Iterate2D f t1 t2)
| _ -> failwith "Iterate2D"
let TryD f g =
match f() with
| ErrorResult(warns, err) -> (OkResult(warns, ())) ++ (fun () -> g err)
| res -> res
let rec RepeatWhileD ndeep body = body ndeep ++ (fun x -> if x then RepeatWhileD (ndeep+1) body else CompleteD)
let AtLeastOneD f l = MapD f l ++ (fun res -> ResultD (List.exists id res))
// Code below is for --flaterrors flag that is only used by the IDE
let stringThatIsAProxyForANewlineInFlatErrors = new System.String [|char 29 |]
let NewlineifyErrorString (message:string) = message.Replace(stringThatIsAProxyForANewlineInFlatErrors, Environment.NewLine)
/// fixes given string by replacing all control chars with spaces.
/// NOTE: newlines are recognized and replaced with stringThatIsAProxyForANewlineInFlatErrors (ASCII 29, the 'group separator'),
/// which is decoded by the IDE with 'NewlineifyErrorString' back into newlines, so that multi-line errors can be displayed in QuickInfo
let NormalizeErrorString (text : string) =
if isNull text then nullArg "text"
let text = text.Trim()
let buf = System.Text.StringBuilder()
let mutable i = 0
while i < text.Length do
let delta =
match text.[i] with
| '\r' when i + 1 < text.Length && text.[i + 1] = '\n' ->
// handle \r\n sequence - replace it with one single space
buf.Append(stringThatIsAProxyForANewlineInFlatErrors) |> ignore
2
| '\n' | '\r' ->
buf.Append(stringThatIsAProxyForANewlineInFlatErrors) |> ignore
1
| c ->
// handle remaining chars: control - replace with space, others - keep unchanged
let c = if Char.IsControl(c) then ' ' else c
buf.Append(c) |> ignore
1
i <- i + delta
buf.ToString()
type public FSharpErrorSeverityOptions =
{
WarnLevel: int
GlobalWarnAsError: bool
WarnOff: int list
WarnOn: int list
WarnAsError: int list
WarnAsWarn: int list
}
static member Default =
{
WarnLevel = 3
GlobalWarnAsError = false
WarnOff = []
WarnOn = []
WarnAsError = []
WarnAsWarn = []
}