-
Notifications
You must be signed in to change notification settings - Fork 770
/
DiagnosticsLogger.fs
901 lines (722 loc) · 31.6 KB
/
DiagnosticsLogger.fs
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
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
module FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.Features
open FSharp.Compiler.Text.Range
open FSharp.Compiler.Text
open System
open System.Diagnostics
open System.Reflection
open System.Threading
open Internal.Utilities.Library
open Internal.Utilities.Library.Extras
open System.Collections.Concurrent
/// Represents the style being used to format errors
[<RequireQualifiedAccess>]
type DiagnosticStyle =
| Default
| Emacs
| Test
| VisualStudio
| Gcc
/// 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 = (string -> unit) -> unit
let NoSuggestions: Suggestions = ignore
/// Thrown when we stop processing the F# Interactive entry or #load.
exception StopProcessingExn of exn option with
override _.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"
[<return: Struct>]
let (|StopProcessing|_|) exn =
match exn with
| StopProcessingExn _ -> ValueSome()
| _ -> ValueNone
let StopProcessing<'T> = StopProcessingExn None
// int is e.g. 191 in FS0191
exception DiagnosticWithText of number: int * message: string * range: range with
override this.Message =
match this :> exn with
| DiagnosticWithText(_, msg, _) -> msg
| _ -> "impossible"
exception InternalError of message: string * range: range with
override this.Message =
match this :> exn with
| InternalError(msg, m) -> msg + m.ToString()
| _ -> "impossible"
exception InternalException of exn: Exception * msg: string * range: range with
override this.Message =
match this :> exn with
| InternalException(_, msg, _) -> msg
| _ -> "impossible"
override this.ToString() =
match this :> exn with
| InternalException(exn, _, _) -> exn.ToString()
| _ -> "impossible"
exception UserCompilerMessage of message: string * number: int * range: range
exception LibraryUseOnly of range: range
exception Deprecated of message: string * range: range
exception Experimental of message: string * range: range
exception PossibleUnverifiableCode of range: range
exception UnresolvedReferenceNoRange of assemblyName: string
exception UnresolvedReferenceError of assemblyName: string * range: range
exception UnresolvedPathReferenceNoRange of assemblyName: string * path: string with
override this.Message =
match this :> exn with
| UnresolvedPathReferenceNoRange(assemblyName, path) -> sprintf "Assembly: %s, full path: %s" assemblyName path
| _ -> "impossible"
exception UnresolvedPathReference of assemblyName: string * path: string * range: range
exception DiagnosticWithSuggestions of number: int * message: string * range: range * identifier: string * suggestions: Suggestions with // int is e.g. 191 in FS0191
override this.Message =
match this :> exn with
| DiagnosticWithSuggestions(_, msg, _, _, _) -> msg
| _ -> "impossible"
/// A diagnostic that is raised when enabled manually, or by default with a language feature
exception DiagnosticEnabledWithLanguageFeature of number: int * message: string * range: range * enabledByLangFeature: bool
/// The F# compiler code currently uses 'Error(...)' in many places to create
/// an DiagnosticWithText as an exception even if it's a warning.
///
/// We will eventually rename this to remove this use of "Error"
let Error ((n, text), m) = DiagnosticWithText(n, text, m)
/// The F# compiler code currently uses 'ErrorWithSuggestions(...)' in many places to create
/// an DiagnosticWithText as an exception even if it's a warning.
///
/// We will eventually rename this to remove this use of "Error"
let ErrorWithSuggestions ((n, message), m, id, suggestions) =
DiagnosticWithSuggestions(n, message, m, id, suggestions)
let ErrorEnabledWithLanguageFeature ((n, message), m, enabledByLangFeature) =
DiagnosticEnabledWithLanguageFeature(n, message, m, enabledByLangFeature)
let inline protectAssemblyExploration dflt ([<InlineIfLambda>] f) =
try
f ()
with
| UnresolvedPathReferenceNoRange _ -> dflt
| _ -> reraise ()
let inline protectAssemblyExplorationF dflt ([<InlineIfLambda>] f) =
try
f ()
with
| UnresolvedPathReferenceNoRange(asmName, path) -> dflt (asmName, path)
| _ -> reraise ()
let inline protectAssemblyExplorationNoReraise dflt1 dflt2 ([<InlineIfLambda>] f) =
try
f ()
with
| UnresolvedPathReferenceNoRange _ -> dflt1
| _ -> dflt2
// Attach a range if this is a range dual exception.
let rec AttachRange m (exn: exn) =
if equals m range0 then
exn
else
match exn with
// Strip TargetInvocationException wrappers
| :? TargetInvocationException -> AttachRange m exn.InnerException
| UnresolvedReferenceNoRange a -> UnresolvedReferenceError(a, m)
| UnresolvedPathReferenceNoRange(a, p) -> UnresolvedPathReference(a, p, m)
| :? NotSupportedException -> exn
| :? SystemException -> InternalException(exn, exn.Message, m)
| _ -> exn
type Exiter =
abstract Exit: int -> 'T
let QuitProcessExiter =
{ new Exiter with
member _.Exit n =
try
Environment.Exit n
with _ ->
()
failwith (FSComp.SR.elSysEnvExitDidntExit ())
}
type StopProcessingExiter() =
member val ExitCode = 0 with get, set
interface Exiter with
member exiter.Exit n =
exiter.ExitCode <- n
raise StopProcessing
/// 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
[<DebuggerDisplay("{DebugDisplay()}")>]
type PhasedDiagnostic =
{
Exception: exn
Phase: BuildPhase
}
/// Construct a phased error
static member Create(exn: exn, phase: BuildPhase) : PhasedDiagnostic = { 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
| 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 ->
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() =
match pe.Phase with
| BuildPhase.Compile
| BuildPhase.Parameter
| BuildPhase.Parse
| BuildPhase.TypeCheck -> true
| _ -> false
[<AbstractClass>]
[<DebuggerDisplay("{DebugDisplay()}")>]
type DiagnosticsLogger(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: diagnostic: PhasedDiagnostic * severity: FSharpDiagnosticSeverity -> unit
member x.CheckForErrors() = (x.ErrorCount > 0)
member _.DebugDisplay() =
sprintf "DiagnosticsLogger(%s)" nameForDebugging
let DiscardErrorsLogger =
{ new DiagnosticsLogger("DiscardErrorsLogger") with
member _.DiagnosticSink(diagnostic, severity) = ()
member _.ErrorCount = 0
}
let AssertFalseDiagnosticsLogger =
{ new DiagnosticsLogger("AssertFalseDiagnosticsLogger") with
// TODO: reenable these asserts in the compiler service
member _.DiagnosticSink(diagnostic, severity) = (* assert false; *) ()
member _.ErrorCount = (* assert false; *) 0
}
type CapturingDiagnosticsLogger(nm, ?eagerFormat) =
inherit DiagnosticsLogger(nm)
let mutable errorCount = 0
let diagnostics = ResizeArray()
override _.DiagnosticSink(diagnostic, severity) =
let diagnostic =
match eagerFormat with
| None -> diagnostic
| Some f -> f diagnostic
if severity = FSharpDiagnosticSeverity.Error then
errorCount <- errorCount + 1
diagnostics.Add(diagnostic, severity)
override _.ErrorCount = errorCount
member _.Diagnostics = diagnostics |> Seq.toList
member _.CommitDelayedDiagnostics(diagnosticsLogger: DiagnosticsLogger) =
// Eagerly grab all the errors and warnings from the mutable collection
let errors = diagnostics.ToArray()
errors |> Array.iter diagnosticsLogger.DiagnosticSink
/// Type holds thread-static globals for use by the compiler.
type internal DiagnosticsThreadStatics =
[<ThreadStatic; DefaultValue>]
static val mutable private buildPhase: BuildPhase
[<ThreadStatic; DefaultValue>]
static val mutable private diagnosticsLogger: DiagnosticsLogger
static member BuildPhaseUnchecked = DiagnosticsThreadStatics.buildPhase
static member BuildPhase
with get () =
match box DiagnosticsThreadStatics.buildPhase with
| Null -> BuildPhase.DefaultPhase
| _ -> DiagnosticsThreadStatics.buildPhase
and set v = DiagnosticsThreadStatics.buildPhase <- v
static member DiagnosticsLogger
with get () =
match box DiagnosticsThreadStatics.diagnosticsLogger with
| Null -> AssertFalseDiagnosticsLogger
| _ -> DiagnosticsThreadStatics.diagnosticsLogger
and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v
[<AutoOpen>]
module DiagnosticsLoggerExtensions =
// Dev15.0 shipped with a bug in diasymreader in the portable pdb symbol reader which causes an AV
// This uses a simple heuristic to detect it (the vsversion is < 16.0)
let tryAndDetectDev15 =
let vsVersion = Environment.GetEnvironmentVariable("VisualStudioVersion")
match Double.TryParse vsVersion with
| true, v -> v < 16.0
| _ -> false
/// Instruct the exception not to reset itself when thrown again.
let PreserveStackTrace exn =
try
if not tryAndDetectDev15 then
let preserveStackTrace =
typeof<Exception>
.GetMethod("InternalPreserveStackTrace", BindingFlags.Instance ||| BindingFlags.NonPublic)
preserveStackTrace.Invoke(exn, null) |> ignore
with _ ->
// This is probably only the mono case.
Debug.Assert(false, "Could not preserve stack trace for watson exception.")
()
type DiagnosticsLogger with
member x.EmitDiagnostic(exn, severity) =
match exn with
| InternalError(s, _)
| InternalException(_, s, _)
| Failure s as exn -> 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, DiagnosticsThreadStatics.BuildPhase), severity)
member x.ErrorR exn =
x.EmitDiagnostic(exn, FSharpDiagnosticSeverity.Error)
member x.Warning exn =
x.EmitDiagnostic(exn, FSharpDiagnosticSeverity.Warning)
member x.InformationalWarning exn =
x.EmitDiagnostic(exn, FSharpDiagnosticSeverity.Info)
member x.Error exn =
x.ErrorR exn
raise (ReportedError(Some exn))
member x.SimulateError diagnostic =
x.DiagnosticSink(diagnostic, FSharpDiagnosticSeverity.Error)
raise (ReportedError(Some diagnostic.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
| :? System.Threading.ThreadAbortException
| WrappedError(:? System.Threading.ThreadAbortException, _) -> ()
| 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.
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 UseBuildPhase (phase: BuildPhase) =
let oldBuildPhase = DiagnosticsThreadStatics.BuildPhaseUnchecked
DiagnosticsThreadStatics.BuildPhase <- phase
{ new IDisposable with
member x.Dispose() =
DiagnosticsThreadStatics.BuildPhase <- oldBuildPhase
}
/// NOTE: The change will be undone when the returned "unwind" object disposes
let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #DiagnosticsLogger) =
let oldLogger = DiagnosticsThreadStatics.DiagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLogger <- transformer oldLogger
{ new IDisposable with
member _.Dispose() =
DiagnosticsThreadStatics.DiagnosticsLogger <- oldLogger
}
let UseDiagnosticsLogger newLogger =
UseTransformedDiagnosticsLogger(fun _ -> newLogger)
let SetThreadBuildPhaseNoUnwind (phase: BuildPhase) =
DiagnosticsThreadStatics.BuildPhase <- phase
let SetThreadDiagnosticsLoggerNoUnwind diagnosticsLogger =
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
/// This represents the thread-local state established as each task function runs as part of the build.
///
/// Use to reset error and warning handlers.
type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: BuildPhase) =
let unwindEL = UseDiagnosticsLogger diagnosticsLogger
let unwindBP = UseBuildPhase buildPhase
member _.DiagnosticsLogger = diagnosticsLogger
member _.BuildPhase = buildPhase
// Return the disposable object that cleans up
interface IDisposable with
member _.Dispose() =
unwindBP.Dispose()
unwindEL.Dispose()
// Global functions are still used by parser and TAST ops.
/// Raises an exception with error recovery and returns unit.
let errorR exn =
DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR exn
/// Raises a warning with error recovery and returns unit.
let warning exn =
DiagnosticsThreadStatics.DiagnosticsLogger.Warning exn
/// Raises a warning with error recovery and returns unit.
let informationalWarning exn =
DiagnosticsThreadStatics.DiagnosticsLogger.InformationalWarning exn
/// Raises a special exception and returns 'T - can be caught later at an errorRecovery point.
let error exn =
DiagnosticsThreadStatics.DiagnosticsLogger.Error exn
/// Simulates an error. For test purposes only.
let simulateError (diagnostic: PhasedDiagnostic) =
DiagnosticsThreadStatics.DiagnosticsLogger.SimulateError diagnostic
let diagnosticSink (diagnostic, severity) =
DiagnosticsThreadStatics.DiagnosticsLogger.DiagnosticSink(diagnostic, severity)
let errorSink diagnostic =
diagnosticSink (diagnostic, FSharpDiagnosticSeverity.Error)
let warnSink diagnostic =
diagnosticSink (diagnostic, FSharpDiagnosticSeverity.Warning)
let errorRecovery exn m =
DiagnosticsThreadStatics.DiagnosticsLogger.ErrorRecovery exn m
let stopProcessingRecovery exn m =
DiagnosticsThreadStatics.DiagnosticsLogger.StopProcessingRecovery exn m
let errorRecoveryNoRange exn =
DiagnosticsThreadStatics.DiagnosticsLogger.ErrorRecoveryNoRange exn
let deprecatedWithError s m = errorR (Deprecated(s, m))
let libraryOnlyError m = errorR (LibraryUseOnly m)
let libraryOnlyWarning m = warning (LibraryUseOnly m)
let deprecatedOperator m =
deprecatedWithError (FSComp.SR.elDeprecatedOperator ()) m
let mlCompatWarning s m =
warning (UserCompilerMessage(FSComp.SR.mlCompatMessage s, 62, m))
let mlCompatError s m =
errorR (UserCompilerMessage(FSComp.SR.mlCompatError s, 62, m))
[<DebuggerStepThrough>]
let suppressErrorReporting f =
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
try
let diagnosticsLogger =
{ new DiagnosticsLogger("suppressErrorReporting") with
member _.DiagnosticSink(_phasedError, _isError) = ()
member _.ErrorCount = 0
}
SetThreadDiagnosticsLoggerNoUnwind diagnosticsLogger
f ()
finally
SetThreadDiagnosticsLoggerNoUnwind diagnosticsLogger
[<DebuggerStepThrough>]
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
/// The result type of a computational modality to collect warnings and possibly fail
[<NoEquality; NoComparison>]
type OperationResult<'T> =
| OkResult of warnings: exn list * result: 'T
| ErrorResult of warnings: exn list * error: 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 inline ErrorD err = ErrorResult([], err)
let inline WarnD err = OkResult([ err ], ())
let CompleteD = OkResult([], ())
let inline ResultD x = OkResult([], x)
let CheckNoErrorsAndGetWarnings res =
match res with
| OkResult(warns, res2) -> Some(warns, res2)
| ErrorResult _ -> None
[<DebuggerHidden; DebuggerStepThrough>]
let inline bind f res =
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.
[<DebuggerHidden; DebuggerStepThrough>]
let rec IterateD f xs =
match xs with
| [] -> CompleteD
| h :: t -> f h |> bind (fun () -> IterateD f t)
[<DebuggerHidden; DebuggerStepThrough>]
let rec WhileD gd body =
if gd () then
body () |> bind (fun () -> WhileD gd body)
else
CompleteD
[<DebuggerHidden; DebuggerStepThrough>]
let rec MapD_loop f acc xs =
match xs with
| [] -> ResultD(List.rev acc)
| h :: t -> f h |> bind (fun x -> MapD_loop f (x :: acc) t)
[<DebuggerHidden; DebuggerStepThrough>]
let MapD f xs = MapD_loop f [] xs
type TrackErrorsBuilder() =
member inline x.Bind(res, k) = bind k res
member inline x.Return res = ResultD res
member inline x.ReturnFrom res = res
member inline x.For(seq, k) = IterateD k seq
member inline x.Combine(expr1, expr2) = bind expr2 expr1
member inline x.While(gd, k) = WhileD gd k
member inline x.Zero() = CompleteD
member inline x.Delay(fn: unit -> _) = fn
member inline x.Run fn = fn ()
let trackErrors = TrackErrorsBuilder()
/// Stop on first error. Accumulate warnings and continue.
[<DebuggerHidden; DebuggerStepThrough>]
let OptionD f xs =
match xs with
| None -> CompleteD
| Some h -> f h
/// Stop on first error. Report index
[<DebuggerHidden; DebuggerStepThrough>]
let IterateIdxD f xs =
let rec loop xs i =
match xs with
| [] -> CompleteD
| h :: t -> f i h |> bind (fun () -> loop t (i + 1))
loop xs 0
/// Stop on first error. Accumulate warnings and continue.
[<DebuggerHidden; DebuggerStepThrough>]
let rec Iterate2D f xs ys =
match xs, ys with
| [], [] -> CompleteD
| h1 :: t1, h2 :: t2 -> f h1 h2 |> bind (fun () -> Iterate2D f t1 t2)
| _ -> failwith "Iterate2D"
/// Keep the warnings, propagate the error to the exception continuation.
[<DebuggerHidden; DebuggerStepThrough>]
let TryD f g =
match f () with
| ErrorResult(warns, err) ->
trackErrors {
do! OkResult(warns, ())
return! g err
}
| res -> res
[<DebuggerHidden; DebuggerStepThrough>]
let rec RepeatWhileD nDeep body =
body nDeep
|> bind (fun x -> if x then RepeatWhileD (nDeep + 1) body else CompleteD)
[<DebuggerHidden; DebuggerStepThrough>]
let inline AtLeastOneD f l =
MapD f l |> bind (fun res -> ResultD(List.exists id res))
[<DebuggerHidden; DebuggerStepThrough>]
let inline AtLeastOne2D f xs ys =
List.zip xs ys |> AtLeastOneD(fun (x, y) -> f x y)
[<DebuggerHidden; DebuggerStepThrough>]
let inline MapReduceD mapper zero reducer l =
MapD mapper l
|> bind (fun res ->
ResultD(
match res with
| [] -> zero
| _ -> List.reduce reducer res
))
[<DebuggerHidden; DebuggerStepThrough>]
let inline MapReduce2D mapper zero reducer xs ys =
List.zip xs ys |> MapReduceD (fun (x, y) -> mapper x y) zero reducer
[<RequireQualifiedAccess>]
module OperationResult =
let inline ignore (res: OperationResult<'a>) =
match res with
| OkResult(warnings, _) -> OkResult(warnings, ())
| ErrorResult(warnings, err) -> ErrorResult(warnings, err)
// Code below is for --flaterrors flag that is only used by the IDE
let stringThatIsAProxyForANewlineInFlatErrors = 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 MaybeNull) =
let text = nullArgCheck "text" 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()
/// Indicates whether a language feature check should be skipped. Typically used in recursive functions
/// where we don't want repeated recursive calls to raise the same diagnostic multiple times.
[<RequireQualifiedAccess>]
type internal SuppressLanguageFeatureCheck =
| Yes
| No
let internal languageFeatureError (langVersion: LanguageVersion) (langFeature: LanguageFeature) (m: range) =
let featureStr = LanguageVersion.GetFeatureString langFeature
let currentVersionStr = langVersion.SpecifiedVersionString
let suggestedVersionStr = LanguageVersion.GetFeatureVersionString langFeature
Error(FSComp.SR.chkFeatureNotLanguageSupported (featureStr, currentVersionStr, suggestedVersionStr), m)
let private tryLanguageFeatureErrorAux (langVersion: LanguageVersion) (langFeature: LanguageFeature) (m: range) =
if not (langVersion.SupportsFeature langFeature) then
Some(languageFeatureError langVersion langFeature m)
else
None
let internal checkLanguageFeatureError langVersion langFeature m =
match tryLanguageFeatureErrorAux langVersion langFeature m with
| Some e -> error e
| None -> ()
let internal checkLanguageFeatureAndRecover langVersion langFeature m =
match tryLanguageFeatureErrorAux langVersion langFeature m with
| Some e -> errorR e
| None -> ()
let internal tryLanguageFeatureErrorOption langVersion langFeature m =
tryLanguageFeatureErrorAux langVersion langFeature m
let internal languageFeatureNotSupportedInLibraryError (langFeature: LanguageFeature) (m: range) =
let featureStr = LanguageVersion.GetFeatureString langFeature
let suggestedVersionStr = LanguageVersion.GetFeatureVersionString langFeature
error (Error(FSComp.SR.chkFeatureNotSupportedInLibrary (featureStr, suggestedVersionStr), m))
/// Guard against depth of expression nesting, by moving to new stack when a maximum depth is reached
type StackGuard(maxDepth: int, name: string) =
let mutable depth = 1
[<DebuggerHidden; DebuggerStepThrough>]
member _.Guard(f) =
depth <- depth + 1
try
if depth % maxDepth = 0 then
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let buildPhase = DiagnosticsThreadStatics.BuildPhase
let ct = Cancellable.Token
async {
do! Async.SwitchToNewThread()
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depth})"
use _scope = new CompilationGlobalsScope(diagnosticsLogger, buildPhase)
use _token = Cancellable.UsingToken ct
return f ()
}
|> Async.RunImmediate
else
f ()
finally
depth <- depth - 1
static member val DefaultDepth =
#if DEBUG
GetEnvInteger "FSHARP_DefaultStackGuardDepth" 50
#else
GetEnvInteger "FSHARP_DefaultStackGuardDepth" 100
#endif
static member GetDepthOption(name: string) =
GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth
type CaptureDiagnosticsConcurrently() =
let target = DiagnosticsThreadStatics.DiagnosticsLogger
let loggers = ResizeArray()
member _.GetLoggerForTask(name) : DiagnosticsLogger =
let logger = CapturingDiagnosticsLogger(name)
loggers.Add logger
logger
interface IDisposable with
member _.Dispose() =
for logger in loggers do
logger.CommitDelayedDiagnostics target