// Copyright by the contributors to the Dafny Project // SPDX-License-Identifier: MIT using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.CommandLine; using System.CommandLine.Binding; using System.Diagnostics; using System.Globalization; using System.Linq; using System.IO; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Dafny; using Microsoft.Dafny.Compilers; using Microsoft.Dafny.Plugins; using Bpl = Microsoft.Boogie; namespace Microsoft.Dafny { public enum FunctionSyntaxOptions { Version3, Migration3To4, ExperimentalTreatUnspecifiedAsGhost, ExperimentalTreatUnspecifiedAsCompiled, ExperimentalPredicateAlwaysGhost, Version4, } public enum QuantifierSyntaxOptions { Version3, Version4, } public interface IDafnyOutputWriter { /// /// Provide debugging information that's useful to a Dafny language developer /// void Debug(string message); /// /// Provide debugging information that's useful to a Dafny language developer /// [StringFormatMethod(nameof(message))] void Debug(string message, params object[] arguments) { Debug(string.Format(message, arguments)); } /// /// When Dafny reaches a bad state, this can be used to provide debugging information. /// void Exception(string message); /// /// Provide progress updates and summary information /// Task Status(string message); /// /// Output a piece of Dafny code /// Task Code(string message); /// /// Closing the writer will print a status message /// TextWriter StatusWriter(); /// /// Closing the writer will print an error message /// TextWriter ErrorWriter(); /// /// Emit a diagnostic /// void WriteDiagnostic(DafnyDiagnostic diagnostic); /// /// Like Status, but uses stderr if possible /// /// /// Task Error(string message); } public record Options(Dictionary OptionArguments, Dictionary Arguments); public class DafnyOptions : Bpl.CommandLineOptions { public TextWriter BaseOutputWriter => base.OutputWriter; public new IDafnyOutputWriter OutputWriter => Get(CommonOptionBag.JsonOutput) ? new JsonOutputWriter(this) : new HumanReadableOutputWriter(this); public string GetPrintPath(string path) => UseBaseNameForFileName ? Path.GetFileName(path) : path; public TextWriter ErrorWriter { get; set; } public TextReader Input { get; } public static readonly DafnyOptions Default = new(TextReader.Null, TextWriter.Null, TextWriter.Null); public IList CliRootSourceUris = new List(); public DafnyProject DafnyProject { get; set; } public static void ParseDefaultFunctionOpacity(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { if (ps.ConfirmArgumentCount(1)) { if (ps.args[ps.i].Equals("transparent")) { options.Set(option, CommonOptionBag.DefaultFunctionOpacityOptions.Transparent); } else if (ps.args[ps.i].Equals("autoRevealDependencies")) { options.Set(option, CommonOptionBag.DefaultFunctionOpacityOptions.AutoRevealDependencies); } else if (ps.args[ps.i].Equals("opaque")) { options.Set(option, CommonOptionBag.DefaultFunctionOpacityOptions.Opaque); } else { InvalidArgumentError(option.Name, ps); } } } public void ApplyBinding(Option option) { if (legacyBindings.ContainsKey(option)) { legacyBindings[option](this, Get(option)); } } public T Get(Argument argument) { return (T)Options.Arguments.GetOrDefault(argument, () => (object)default(T)); } public T Get(Option option) { return (T)Options.OptionArguments.GetOrDefault(option, () => (object)default(T)); } public T GetOrOptionDefault(Option option) { return (T)Options.OptionArguments.GetOrDefault(option, () => ((IValueDescriptor)option) is { HasDefaultValue: true } valueDescriptor ? valueDescriptor.GetDefaultValue() : (object)default(T) ); } public object Get(Option option) { return Options.OptionArguments[option]; } public void SetUntyped(Option option, object value) { Options.OptionArguments[option] = value; } public void Set(Option option, T value) { Options.OptionArguments[option] = value; } protected override void AddFile(string file, Bpl.CommandLineParseState ps) { CliRootSourceUris.Add(new Uri(Path.GetFullPath(file))); base.AddFile(file, ps); } private static Dictionary> legacyBindings = new(); public static void RegisterLegacyBinding(Option option, Action bind) { legacyBindings[option] = (options, o) => bind(options, (T)o); } public static void ParseFileInfo(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { if (ps.ConfirmArgumentCount(1)) { options.Set(option, new FileInfo(ps.args[ps.i])); } } public static void ParseFileInfoElement(Option> option, Bpl.CommandLineParseState ps, DafnyOptions options) { var value = (IList)options.Options.OptionArguments.GetOrCreate(option, () => new List()); if (ps.ConfirmArgumentCount(1)) { value.Add(new FileInfo(ps.args[ps.i])); } } public static void ParseString(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { if (ps.ConfirmArgumentCount(1)) { options.Set(option, ps.args[ps.i]); } } public static void ParseStringElement(Option> option, Bpl.CommandLineParseState ps, DafnyOptions options) { var value = (IList)options.Options.OptionArguments.GetOrCreate(option, () => new List()); if (ps.ConfirmArgumentCount(1)) { value.Add(ps.args[ps.i]); } } public static void ParseImplicitEnable(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { options.Set(option, true); } public static void ParseBoolean(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { int result = 0; if (ps.GetIntArgument(ref result, 2)) { options.Set(option, result == 1); } } public static void ParseGeneralTraitsOption(Option option, Bpl.CommandLineParseState ps, DafnyOptions options) { if (ps.ConfirmArgumentCount(1)) { switch (ps.args[ps.i]) { case "legacy": options.Set(option, CommonOptionBag.GeneralTraitsOptions.Legacy); break; case "datatype": options.Set(option, CommonOptionBag.GeneralTraitsOptions.Datatype); break; case "full": options.Set(option, CommonOptionBag.GeneralTraitsOptions.Full); break; default: InvalidArgumentError(option.Name, ps); break; } } } private static readonly List LegacyUis = []; public static void RegisterLegacyUi(Option option, Action, Bpl.CommandLineParseState, DafnyOptions> parse, string category, string legacyName = null, string legacyDescription = null, T defaultValue = default(T), string argumentName = null) { LegacyUis.Add(new LegacyUiForOption( option, (state, options) => parse(option, state, options), category, legacyName ?? option.Name, legacyDescription ?? option.Description, argumentName ?? option.ArgumentHelpName ?? "value", defaultValue)); } private static DafnyOptions defaultImmutableOptions; public static DafnyOptions DefaultImmutableOptions => defaultImmutableOptions ??= CreateUsingOldParser(Console.Out, Console.In); public static DafnyOptions CreateUsingOldParser(TextWriter outputWriter, TextReader input = null, params string[] arguments) { input ??= TextReader.Null; var result = new DafnyOptions(input, outputWriter, outputWriter); result.Parse(arguments); return result; } public override bool Parse(string[] arguments) { int i; for (i = 0; i < arguments.Length; i++) { if (arguments[i] == "--args") { break; } } try { if (i >= arguments.Length) { return BaseParse(arguments, true); } MainArgs = arguments.Skip(i + 1).ToList(); return BaseParse(arguments.Take(i).ToArray(), true); } catch (Exception e) { ErrorWriter.WriteLine("Invalid filename: " + e.Message); return false; } } protected override Bpl.CommandLineParseState InitializeCommandLineParseState(string[] args) { return new TextWriterParseState(args, ToolName, ErrorWriter); } /// /// Needed because the Boogie version writes to Console.Error /// class TextWriterParseState : Bpl.CommandLineParseState { private readonly TextWriter errorWriter; public TextWriterParseState(string[] args, string toolName, TextWriter errorWriter) : base(args, toolName) { this.errorWriter = errorWriter; } public override void Error(string message, params string[] args) { errorWriter.WriteLine("{0}: Error: {1}", ToolName, string.Format(message, args)); EncounteredErrors = true; } } /// /// Customized version of Microsoft.Boogie.CommandLineOptions.Parse /// Needed because the Boogie version writes to Console.Error /// public bool BaseParse(string[] args, bool allowFile) { Environment = Environment + "Command Line Options: " + string.Join(" ", args); args = Cce.NonNull((string[])args.Clone()); Bpl.CommandLineParseState state; for (state = InitializeCommandLineParseState(args); state.i < args.Length; state.i = state.nextIndex) { Cce.LoopInvariant(state.args == args); string file = args[state.i]; state.s = file.Trim(); bool flag = state.s.StartsWith("-") || state.s.StartsWith("/"); int length = state.s.IndexOf(':'); if (0 <= length & flag) { state.hasColonArgument = true; args[state.i] = state.s.Substring(length + 1); state.s = state.s.Substring(0, length); } else { ++state.i; state.hasColonArgument = false; } state.nextIndex = state.i; if (flag) { if (!ParseOption(state.s.Substring(1), state)) { if (Path.DirectorySeparatorChar == '/' && state.s.StartsWith("/")) { AddFile(file, state); } else { UnknownSwitch(state); } } } else if (allowFile) { AddFile(file, state); } else { state.Error($"Boogie option '{state.s}' must start with - or /"); } } if (state.EncounteredErrors) { ErrorWriter.WriteLine("Use /help for available options"); return false; } ApplyDefaultOptions(); return true; } public DafnyOptions(TextReader inputReader, TextWriter outputWriter, TextWriter errorWriter) : base(outputWriter, "dafny", "Dafny program verifier", new Bpl.ConsolePrinter()) { Input = inputReader; ErrorWriter = errorWriter; ErrorTrace = 0; Prune = true; TypeEncodingMethod = Bpl.CoreOptions.TypeEncoding.Arguments; NormalizeNames = true; EmitDebugInformation = false; Backend = new CsharpBackend(this); Printer = new NullPrinter(); } public override string VersionNumber { get { return FileVersionInfo .GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; } } public Options Options { get; set; } = new(new Dictionary(), new Dictionary()); public override string Version { get { return ToolName + VersionSuffix; } } public override string VersionSuffix { get { return " " + VersionNumber; } } public bool RunLanguageServer { get; set; } public enum DiagnosticsFormats { PlainText, JSON, } public bool UsingNewCli = false; public bool UnicodeOutput = false; public DiagnosticsFormats DiagnosticsFormat = DiagnosticsFormats.PlainText; public bool DisallowSoundnessCheating = false; public int Induction = 4; public int InductionHeuristic = 6; public string DafnyPrelude = null; public string DafnyPrintFile = null; public bool AllowSourceFolders = false; public List SourceFolders { get; } = []; // list of folders, for those commands that permit processing all source files in folders public enum ContractTestingMode { None, Externs, TestedExterns, } public PrintModes PrintMode = PrintModes.Everything; // Default to printing everything public bool DafnyVerify = true; public string DafnyPrintResolvedFile = null; public List DafnyPrintExportedViews = []; public bool Compile = true; public List MainArgs = []; public bool FormatCheck = false; public string CompilerName; public IExecutableBackend Backend; public bool Verbose = true; public bool EnforcePrintEffects = false; public string DafnyPrintCompiledFile = null; public string CoverageLegendFile = null; public string MainMethod = null; public bool ForceCompile = false; public bool RunAfterCompile = false; public uint SpillTargetCode = 0; // [0..4] public bool DisallowIncludes = false; public bool DisallowExterns = false; public bool AllowExterns => !DisallowExterns; public bool DisableNLarith = false; public int ArithMode = 1; // [0..10] public string AutoReqPrintFile = null; public bool ignoreAutoReq = false; public bool Optimize = false; public bool AutoTriggers = true; public bool RewriteFocalPredicates = true; public bool PrintTooltips = false; public bool PrintStats = false; public string MethodsToTest = null; public bool DisallowConstructorCaseWithoutParentheses = false; public bool PrintFunctionCallGraph = false; public bool WarnShadowing = false; public FunctionSyntaxOptions FunctionSyntax = FunctionSyntaxOptions.Version4; public QuantifierSyntaxOptions QuantifierSyntax = QuantifierSyntaxOptions.Version4; public int DefiniteAssignmentLevel { get; set; } = 1; public HashSet LibraryFiles { get; set; } = []; public ContractTestingMode TestContracts = ContractTestingMode.None; public bool ForbidNondeterminism { get; set; } public int DeprecationNoise = 1; public bool VerifyAllModules = false; public bool SeparateModuleOutput = false; public enum IncludesModes { None, Immediate, Transitive } public IncludesModes PrintIncludesMode = IncludesModes.None; public int OptimizeResolution = 2; public bool IncludeRuntime = true; public CommonOptionBag.SystemModuleMode SystemModuleTranslationMode = CommonOptionBag.SystemModuleMode.Omit; public bool UseJavadocLikeDocstringRewriter = false; public bool DisableScopes = false; public bool UseStdin = false; public bool FailOnWarnings = false; [CanBeNull] private TestGenerationOptions testGenOptions = null; public bool ExtractCounterexample = false; public bool ShowProofObligationExpressions = false; public bool AuditProgram = false; public static string DefaultZ3Version = "4.12.1"; // Not directly user-configurable, only recorded once we discover it public string SolverIdentifier { get; private set; } public Version SolverVersion { get; set; } public static readonly ReadOnlyCollection DefaultPlugins = new(new[] { SinglePassCodeGenerator.Plugin, InternalDocstringRewritersPluginConfiguration.Plugin }); private IList cliPluginCache; public IList Plugins => cliPluginCache ??= ComputePlugins(AdditionalPlugins, AdditionalPluginArguments); public List AdditionalPlugins = []; public IList AdditionalPluginArguments = new List(); public static IList ComputePlugins(List additionalPlugins, IList allArguments) { var result = new List(DefaultPlugins.Concat(additionalPlugins)); foreach (var pluginAndArgument in allArguments) { try { var pluginArray = pluginAndArgument.Split(','); var pluginPath = pluginArray[0]; var arguments = Array.Empty(); if (pluginArray.Length >= 2) { // There are no commas in paths, but there can be in arguments var argumentsString = string.Join(',', pluginArray.Skip(1)); // Parse arguments, accepting and remove double quotes that isolate long arguments arguments = ParsePluginArguments(argumentsString); } result.Add(AssemblyPlugin.Load(pluginPath, arguments)); } catch (Exception e) { result.Add(new ErrorPlugin(pluginAndArgument, e)); } } return result; } private static string[] ParsePluginArguments(string argumentsString) { var splitter = new Regex(@"""(?(?:[^""\\]|\\\\|\\"")*)""|(?[^ ]+)"); var escapedChars = new Regex(@"(?\\"")|\\\\"); return splitter.Matches(argumentsString).Select( matchResult => matchResult.Groups["escapedArgument"].Success ? escapedChars.Replace(matchResult.Groups["escapedArgument"].Value, matchResult2 => matchResult2.Groups["escapedDoubleQuote"].Success ? "\"" : "\\") : matchResult.Groups["rawArgument"].Value ).ToArray(); } public static bool TryParseResourceCount(string value, out uint result) { return uint.TryParse(value, NumberStyles.AllowExponent, null, out result); } /// /// Automatic shallow-copy constructor /// public DafnyOptions(DafnyOptions src, bool useNullWriters = false) : this( src.Input, src.BaseOutputWriter, src.ErrorWriter) { src.CopyTo(this, useNullWriters); CliRootSourceUris = new List(src.CliRootSourceUris); ProverOptions = [.. src.ProverOptions]; Options = new Options( src.Options.OptionArguments.ToDictionary(kv => kv.Key, kv => kv.Value), src.Options.Arguments.ToDictionary(kv => kv.Key, kv => kv.Value)); } private void CopyTo(DafnyOptions dst, bool useNullWriters) { var type = typeof(DafnyOptions); while (type != null) { var fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); foreach (var fi in fields) { var value = fi.GetValue(this); // This hacky code is necessary until we switch to a Boogie version that implements https://github.com/boogie-org/boogie/pull/788 if (useNullWriters && fi.Name is "k__BackingField" or "k__BackingField") { value = TextWriter.Null; } fi.SetValue(dst, value); } type = type.BaseType; } } public virtual TestGenerationOptions TestGenOptions => testGenOptions ??= new TestGenerationOptions(); protected override bool ParseOption(string name, Bpl.CommandLineParseState ps) { if (ParseDafnySpecificOption(name, ps)) { return true; } foreach (var option in LegacyUis.Where(o => o.Name == name)) { option.Parse(ps, this); return true; } return ParseBoogieOption(name, ps); } private bool ParseBoogieOption(string name, Bpl.CommandLineParseState ps) { return base.ParseOption(name, ps); } public override string Help => "Use 'dafny --help' to see help for the new Dafny CLI format.\n" + LegacyUiForOption.GenerateHelp(base.Help, LegacyUis, true); protected bool ParseDafnySpecificOption(string name, Bpl.CommandLineParseState ps) { var args = ps.args; // convenient synonym switch (name) { case "view": if (ps.ConfirmArgumentCount(1)) { DafnyPrintExportedViews = args[ps.i].Split(',').ToList(); } return true; case "compile": { int compile = 0; if (ps.GetIntArgument(ref compile, 5)) { // convert option to two booleans Compile = compile != 0; ForceCompile = compile == 2 || compile == 4; RunAfterCompile = compile == 3 || compile == 4; } return true; } case "compileVerbose": { int verbosity = 0; if (ps.GetIntArgument(ref verbosity, 2)) { Verbose = verbosity == 1; } return true; } case "trackPrintEffects": { int printEffects = 0; if (ps.GetIntArgument(ref printEffects, 2)) { EnforcePrintEffects = printEffects == 1; } return true; } case "Main": case "main": { if (ps.ConfirmArgumentCount(1)) { MainMethod = args[ps.i]; } return true; } case "check": { if (!ps.hasColonArgument || ps.ConfirmArgumentCount(1)) { FormatCheck = !ps.hasColonArgument || args[ps.i] == "1"; } return true; } case "dafnyVerify": { int verify = 0; if (ps.GetIntArgument(ref verify, 2)) { DafnyVerify = verify != 0; // convert to boolean } return true; } case "diagnosticsFormat": { if (ps.ConfirmArgumentCount(1)) { switch (args[ps.i]) { case "json": Printer = new DafnyJsonConsolePrinter(this); DiagnosticsFormat = DiagnosticsFormats.JSON; break; case "text": Printer = new DafnyConsolePrinter(this); DiagnosticsFormat = DiagnosticsFormats.PlainText; break; case var df: ps.Error($"Unsupported diagnostic format: '{df}'; expecting one of 'json', 'text'."); break; } } return true; } case "spillTargetCode": { uint spill = 0; if (ps.GetUnsignedNumericArgument(ref spill, x => true)) { SpillTargetCode = spill; } return true; } case "coverage": { if (ps.ConfirmArgumentCount(1)) { CoverageLegendFile = args[ps.i]; } return true; } case "noCheating": { int cheat = 0; // 0 is default, allows cheating if (ps.GetIntArgument(ref cheat, 2)) { DisallowSoundnessCheating = cheat == 1; } return true; } case "induction": ps.GetIntArgument(ref Induction, 5); return true; case "inductionHeuristic": ps.GetIntArgument(ref InductionHeuristic, 7); return true; case "noIncludes": DisallowIncludes = true; return true; case "noExterns": DisallowExterns = true; return true; case "noNLarith": DisableNLarith = true; return true; case "arith": { int a = 0; if (ps.GetIntArgument(ref a, 11)) { ArithMode = a; } return true; } case "autoReqPrint": if (ps.ConfirmArgumentCount(1)) { AutoReqPrintFile = args[ps.i]; } return true; case "noAutoReq": ignoreAutoReq = true; return true; case "stats": PrintStats = true; return true; case "funcCallGraph": PrintFunctionCallGraph = true; return true; case "warnShadowing": WarnShadowing = true; return true; case "verifyAllModules": VerifyAllModules = true; return true; case "emitUncompilableCode": this.Set(CommonOptionBag.EmitUncompilableCode, true); return true; case "separateModuleOutput": SeparateModuleOutput = true; return true; case "deprecation": { int d = 1; if (ps.GetIntArgument(ref d, 3)) { DeprecationNoise = d; } return true; } case "functionSyntax": if (ps.ConfirmArgumentCount(1)) { if (args[ps.i] == "3") { FunctionSyntax = FunctionSyntaxOptions.Version3; } else if (args[ps.i] == "4") { FunctionSyntax = FunctionSyntaxOptions.Version4; } else if (args[ps.i] == "migration3to4") { FunctionSyntax = FunctionSyntaxOptions.Migration3To4; } else if (args[ps.i] == "experimentalDefaultGhost") { FunctionSyntax = FunctionSyntaxOptions.ExperimentalTreatUnspecifiedAsGhost; } else if (args[ps.i] == "experimentalDefaultCompiled") { FunctionSyntax = FunctionSyntaxOptions.ExperimentalTreatUnspecifiedAsCompiled; } else if (args[ps.i] == "experimentalPredicateAlwaysGhost") { FunctionSyntax = FunctionSyntaxOptions.ExperimentalPredicateAlwaysGhost; } else { InvalidArgumentError(name, ps); } } return true; case "quantifierSyntax": if (ps.ConfirmArgumentCount(1)) { if (args[ps.i] == "3") { QuantifierSyntax = QuantifierSyntaxOptions.Version3; } else if (args[ps.i] == "4") { QuantifierSyntax = QuantifierSyntaxOptions.Version4; } else { InvalidArgumentError(name, ps); } } return true; case "printTooltips": PrintTooltips = true; return true; case "warnMissingConstructorParentheses": DisallowConstructorCaseWithoutParentheses = true; return true; case "autoTriggers": { int autoTriggers = 0; if (ps.GetIntArgument(ref autoTriggers, 2)) { AutoTriggers = autoTriggers == 1; } return true; } case "rewriteFocalPredicates": { int rewriteFocalPredicates = 0; if (ps.GetIntArgument(ref rewriteFocalPredicates, 2)) { RewriteFocalPredicates = rewriteFocalPredicates == 1; } return true; } case "optimize": { Optimize = true; return true; } case "optimizeResolution": { int d = 2; if (ps.GetIntArgument(ref d, 3)) { OptimizeResolution = d; } return true; } case "definiteAssignment": { int da = 0; if (ps.GetIntArgument(ref da, 5)) { DefiniteAssignmentLevel = da; } if (da == 3) { ForbidNondeterminism = true; } return true; } case "useRuntimeLib": { IncludeRuntime = false; return true; } case "disableScopes": { DisableScopes = true; return true; } case "printIncludes": if (ps.ConfirmArgumentCount(1)) { if (args[ps.i].Equals("None")) { PrintIncludesMode = IncludesModes.None; } else if (args[ps.i].Equals("Immediate")) { PrintIncludesMode = IncludesModes.Immediate; } else if (args[ps.i].Equals("Transitive")) { PrintIncludesMode = IncludesModes.Transitive; } else { InvalidArgumentError(name, ps); } if (PrintIncludesMode == IncludesModes.Immediate || PrintIncludesMode == IncludesModes.Transitive) { Compile = false; DafnyVerify = false; } } return true; case "stdin": { UseStdin = true; return true; } case "warningsAsErrors": FailOnWarnings = true; return true; case "extractCounterexample": ExtractCounterexample = true; EnhancedErrorMessages = 1; return true; case "showProofObligationExpressions": ShowProofObligationExpressions = true; return true; case "testContracts": if (ps.ConfirmArgumentCount(1)) { if (args[ps.i].Equals("Externs")) { TestContracts = ContractTestingMode.Externs; } else if (args[ps.i].Equals("TestedExterns")) { TestContracts = ContractTestingMode.TestedExterns; } else { InvalidArgumentError(name, ps); } } return true; } // Defer to superclass return base.ParseOption(name, ps); } private static string[] ParseInnerArguments(string argumentsString) { var splitter = new Regex(@"""(?(?:[^""\\]|\\\\|\\"")*)""|(?[^ ]+)"); var escapedChars = new Regex(@"(?\\"")|\\\\"); return splitter.Matches(argumentsString).Select( matchResult => matchResult.Groups["escapedArgument"].Success ? escapedChars.Replace(matchResult.Groups["escapedArgument"].Value, matchResult2 => matchResult2.Groups["escapedDoubleQuote"].Success ? "\"" : "\\") : matchResult.Groups["rawArgument"].Value ).ToArray(); } public static void InvalidArgumentError(string name, Bpl.CommandLineParseState ps) { ps.Error("Invalid argument \"{0}\" to option {1}", ps.args[ps.i], name); } public override void ApplyDefaultOptions() { foreach (var legacyUiOption in LegacyUis) { if (!Options.OptionArguments.ContainsKey(legacyUiOption.Option)) { Options.OptionArguments[legacyUiOption.Option] = legacyUiOption.DefaultValue; } if (legacyBindings.ContainsKey(legacyUiOption.Option)) { var value = Get(legacyUiOption.Option); legacyBindings[legacyUiOption.Option](this, value); } } ApplyDefaultOptionsWithoutSettingsDefault(); } public void ApplyDefaultOptionsWithoutSettingsDefault() { base.ApplyDefaultOptions(); Backend ??= new CsharpBackend(this); // Ask Boogie to perform abstract interpretation UseAbstractInterpretation = true; Ai.J_Intervals = true; } public bool IsUsingZ3() { return !ProverOptions.Any(x => x.StartsWith("SOLVER=") && !x.EndsWith("=z3")); } public void ProcessSolverOptions(ErrorReporter errorReporter, IOrigin token) { if (IsUsingZ3()) { var z3Version = SetZ3ExecutablePath(errorReporter, token); SetZ3Options(z3Version); } } public override string AttributeHelp => @"Dafny: The documentation about attributes is best viewed here: https://dafny-lang.github.io/dafny/DafnyRef/DafnyRef#sec-attributes The following attributes are supported by this version. {:extern} {:extern } {:extern , } NOTE: :extern is target-language dependent. The extern modifier is used * to alter the CompileName of entities such as modules, classes, methods, etc., * to alter the ReferenceName of the entities, * to decide how to define external abstract types, * to decide whether to emit target code or not, and * to decide whether a declaration is allowed not to have a body. The CompileName is the name for the entity when translating to one of the target languages. The ReferenceName is the name used to refer to the entity in the target language. A common use case of :extern is to avoid name clashes with existing library functions. :extern takes 0, 1, or 2 (possibly empty) string arguments: - 0: Dafny will use the Dafny name as the CompileName and not affect the ReferenceName - 1: Dafny will use s1 as the CompileName, and replaces the last portion of the ReferenceName by s1. When used on an abstract type, s1 is used as a hint as to how to declare that type when compiling. - 2: Dafny will use s2 as the CompileName. Dafny will use a combination of s1 and s2 such as for example s1.s2 as the ReferenceName It may also be the case that one of the arguments is simply ignored. Dafny does not perform sanity checks on the arguments---it is the user's responsibility not to generate malformed target code. {:compile} The {:compile} attribute takes a boolean argument. It may be applied to any top-level declaration. If that argument is false, then that declaration will not be compiled at all. The difference with {:extern} is that {:extern} will still emit declaration code if necessary, whereas {:compile false} will just ignore the declaration for compilation purposes. {:main} When executing a program, Dafny will first look for a method annotated with {:main}, and otherwise will look for `method Main()`, and then execute the first of these two methods found. {:axiom} Ordinarily, the compiler gives an error for every function or method without a body. If the function or method is ghost, then marking it with {:axiom} suppresses the error. The {:axiom} attribute says you're taking responsibility for the existence of a body for the function or method. {:abstemious} TODO {:print} This attributes declares that a method may have print effects, that is, it may use 'print' statements and may call other methods that have print effects. The attribute can be applied to compiled methods, constructors, and iterators, and it gives an error if applied to functions or ghost methods. An overriding method is allowed to use a {:print} attribute only if the overridden method does. Print effects are enforced only with /trackPrintEffects:1. {:nativeType} Can be applied to newtype declarations for integer types and indicates an expectation of what native type (or not) the newtype should compile to. If a newtype declaration has no explicit :nativeType attribute, then the compiler still attempts to find a suitable native numeric type, which is then reflected in an informational message or hovertext. {:nativeType} and {:nativeType true} say that the type is expected to compile to some native numeric type, but leaves it to the compiler to choose which one. If no suitable native target type is found, an error is generated. {:nativeType false} says to avoid using a native numeric type. Instead, the type will be compiled as an unbounded integer. {:nativeType X} where X is one of the following strings: ""byte"" 8 bits, unsigned ""sbyte"" 8 bits, signed ""ushort"" 16 bits, unsigned ""short"" 16 bits, signed ""uint"" 32 bits, unsigned ""int"" 32 bits, signed ""number"" 53 bits, signed ""ulong"" 64 bits, unsigned ""long"" 64 bits, signed says to use the indicated target type. If the target compiler does not support X, then an error is generated. Also, if, after scrutinizing the constraint predicate, the compiler cannot confirm that the type's values will fit in X, an error is generated. {:nativeType XX} where XX is a list of strings from the list above, says to use the first X in XX that the compiler supports. If the compiler doesn't support any native type in XX, then an error is generated. Also, unless the compiler can confirm that all of the listed native types can fit the type's values, an error is generated. {:tailrecursion} Can be applied to methods and functions to direct compilation of recursive calls as tail calls. A method or function is _tail recursive_ if all of the following points apply: * It is not mutually recursive with another method or function. * Ignoring any parts of the method/function body that are ghost, every recursive call is a tail call (that is, the body has no more work to do after a recursive call). Note that any ghost code that follows a recursive method call is ignored. * In the case of a function, the function is not used as a first-class value inside the function body. For a function F, this definition is extended to additionally allow tail calls to appear in simple expressions like ""E + F(...)"" or ""F(...) + E"" for certain operators ""+"" where E does not mention F, provided that all such expressions are compatible. These are called _simple accumulator_ tail calls. By default, Dafny compiles tail recursive methods and functions using tail calls, automatically handling simple accumulator tail calls. {:tailrecursion false} is used to turn off tail calls. {:tailrecursion} or {:tailrecursion true} is used to confirm that the method/function is compiled and tail recursive. If it is not, an error is given. {:termination} Dafny currently lacks the features needed to specify usable termination metrics for trait methods that are dynamically dispatched to method implementations given in other modules. This issue and a sketch of a solution are described in https://github.com/dafny-lang/dafny/issues/1588. Until such features are added to the language, a type `C` that extends a trait `T` must be declared in the same module as `T`. There is, however, an available loophole: if a programmer is willing to take the responsibility that all calls to methods in a trait `T` that dynamically dispatch to implementations in other modules terminate, then the trait `T` can be marked with `{:termination false}`. This will allow `T` to be extended by types declared in modules outside `T`'s module. Caution: This loophole is unsound; that is, if a cross-module dynamic dispatch fails to terminate, then this and other errors in the program may have been overlooked by the verifier. The meaning of `{:termination false}` is defined only on trait declarations. It has no meaning if applied to other declarations. Applying `{:termination false}` to a trait is similar to the effect of declaring each of its methods with `decreases *`, but there are several differences. The biggest difference is that `decreases *` is sound, whereas the attribute is not. As such, `decreases *` cannot be used with functions, lemmas, or ghost methods, and callers of a `decreases *` method must themselves be declared with `decreases *`. In contrast, `{:termination false}` applies to all functions, lemmas, and methods of the trait, and callers do not have to indicate that they are using such a trait. Another difference is that `{:termination false}` does not change checking for intra-module calls. That is, even if a trait is declared with `{:termination false}`, calls to its functions, lemmas, and methods from within the module where the trait is declared are checked for termination in the usual manner. {:options ""/opt0:xyz"", ""/opt1"", ...} When applied to a module, this attribute configures Dafny as if `/opt0:xyz` and `/opt1` had been passed on the command line. Outside of the module, options revert to their previous values. Supported options: %SUPPORTED_OPTIONS%. {:warnShadowing} TODO {:verify} TODO {:autocontracts} TODO {:opaque} TODO {:autoReq} When applied to a function definition, Dafny automatically strengthens that function's `requires` clause sufficiently so that it may call each of the functions that it calls. When applied to a module, this attribute is inherited by every function in the module. The `/autoReqPrint:` option will print out the inferred, stronger requires clauses to the given file. The `/noAutoReq` option instructs Dafny to ignore any `{:autoReq}` attributes. {:timeLimitMultiplier} TODO {:no_inline} When predicates such as `predicate P(x: int) { x % 2 == 0 }` are used in assertions like `assert P(6);`, Dafny will by default try to figure out if it can split the call into multiple assertions that are easier for the verifier. Hence, sometimes, if allowed to do so (e.g. no `{:opaque}`), Dafny will inline the predicate, resulting in, for example, `assert 6 % 2 == 0`. Adding the attribute `{:no_inline}` to a function will prevent the Dafny verifier from inlining it, but unless the function is `{:opaque}` its definition will still be available. This trick can be helpful, for a huge conjunct predicate `P`, assuming that `P(x)` already hold, if we don't want `P` to be opaque, and we `assert P(x)` again. Inlining might result in performance issues because it will have to infer every single conjunct. Adding `{:no_inline}` to the predicate can result in such cases in the verifier being faster. {:nowarn} TODO {:autotriggers} TODO {:trigger} TODO".Replace("%SUPPORTED_OPTIONS%", string.Join(", ", DafnyAttributeOptions.KnownOptions)); private static ConcurrentDictionary z3VersionPerPath = new(); /// /// Dafny releases come with their own copy of Z3, to save users the trouble of having to install extra dependencies. /// For this to work, Dafny first tries any prover path explicitly provided by the user, then looks for for the copy /// distributed with Dafny, and finally looks in any directory in the system PATH environment variable. /// private Version SetZ3ExecutablePath(ErrorReporter errorReporter, IOrigin token) { string confirmedProverPath = null; string nextStepsMessage = $"Please either provide a path to the `z3` executable using the `--solver-path ` option, manually place the `z3` directory next to the `dafny` executable you are using (this directory should contain `bin/z3-{DefaultZ3Version}` or `bin/z3-{DefaultZ3Version}.exe`), or set the PATH environment variable to also include a directory containing the `z3` executable."; // Try an explicitly provided prover path, if there is one. var pp = "PROVER_PATH="; var proverPathOption = ProverOptions.Find(o => o.StartsWith(pp)); if (proverPathOption != null) { var proverPath = proverPathOption.Substring(pp.Length); // Boogie will perform the ultimate test to see if "proverPath" is real--it will attempt to run it. // However, by at least checking if the file exists, we can produce a better error message in common scenarios. // Unfortunately, there doesn't seem to be a portable way of checking whether it's executable. if (!File.Exists(proverPath)) { errorReporter.Error(MessageSource.Verifier, token, $"Z3 not found at {proverPath}. " + nextStepsMessage); return null; } confirmedProverPath = proverPath; } var platform = System.Environment.OSVersion.Platform; var isUnix = platform == PlatformID.Unix || platform == PlatformID.MacOSX; // Next, try looking in a directory relative to Dafny itself. if (confirmedProverPath is null) { var dafnyBinDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var z3LocalBinName = isUnix ? $"z3-{DefaultZ3Version}" : $"z3-{DefaultZ3Version}.exe"; var z3BinPath = Path.Combine(dafnyBinDir, "z3", "bin", z3LocalBinName); if (File.Exists(z3BinPath)) { confirmedProverPath = z3BinPath; } } // Finally, try looking in the system PATH variable. var z3GlobalBinName = isUnix ? "z3" : "z3.exe"; if (confirmedProverPath is null) { confirmedProverPath = System.Environment .GetEnvironmentVariable("PATH")? .Split(isUnix ? ':' : ';') .Select(s => Path.Combine(s, z3GlobalBinName)) .FirstOrDefault(File.Exists); } if (confirmedProverPath is not null) { ProverOptions.Add($"{pp}{confirmedProverPath}"); return z3VersionPerPath.GetOrAdd(confirmedProverPath, GetZ3Version); } errorReporter.Error(MessageSource.Verifier, DafnyProject.StartingToken, "Z3 is not found. " + nextStepsMessage); return null; } private static readonly Regex Z3VersionRegex = new Regex(@"Z3 version (?\d+)\.(?\d+)\.(?\d+)"); [CanBeNull] public static Version GetZ3Version(string proverPath) { var z3Process = new ProcessStartInfo(proverPath, "-version") { CreateNoWindow = true, RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true }; var run = Process.Start(z3Process); if (run == null) { return null; } var actualOutput = run.StandardOutput.ReadToEnd(); run.WaitForExit(); var versionMatch = Z3VersionRegex.Match(actualOutput); if (!versionMatch.Success) { // Might be another solver. return null; } var major = int.Parse(versionMatch.Groups["major"].Value); var minor = int.Parse(versionMatch.Groups["minor"].Value); var patch = int.Parse(versionMatch.Groups["patch"].Value); return new Version(major, minor, patch); } // Set a Z3 option, but only if it is not overwriting an existing option. private void SetZ3Option(string name, string value) { if (!ProverOptions.Any(o => o.StartsWith($"O:{name}="))) { ProverOptions.Add($"O:{name}={value}"); } } public void SetZ3Options(Version z3Version) { // Don't allow changing this once set, just in case: // a DooFile will record this and will get confused if it changes. if ((SolverIdentifier != null && SolverIdentifier != "Z3") || (SolverVersion != null && SolverVersion != z3Version)) { throw new Exception("Attempted to set Z3 options more than once"); } SolverIdentifier = "Z3"; SolverVersion = z3Version; // Boogie sets the following Z3 options by default: // smt.mbqi = false // model.compact = false // model.v2 = true // pp.bv_literals = false // Boogie also used to set the following options, but does not anymore. SetZ3Option("auto_config", "false"); SetZ3Option("type_check", "true"); SetZ3Option("smt.qi.eager_threshold", "44"); SetZ3Option("smt.delay_units", "true"); SetZ3Option("model_evaluator.completion", "true"); SetZ3Option("model.completion", "true"); if (z3Version is null || z3Version < new Version(4, 8, 6)) { SetZ3Option("model_compress", "false"); } else { SetZ3Option("model.compact", "false"); } // This option helps avoid "time travelling triggers". // See: https://github.com/dafny-lang/dafny/discussions/3362 SetZ3Option("smt.case_split", "3"); if (3 <= ArithMode) { SetZ3Option("smt.arith.nl", "false"); } } protected override string HelpBody => DafnyHelpBody + BoogieHelpBody; protected string BoogieHelpBody => base.HelpBody; protected string DafnyHelpBody => $@" All the .dfy files supplied on the command line along with files recursively included by 'include' directives are considered a single Dafny program; however only those files listed on the command line are verified. Exit code: 0 -- success; 1 -- invalid command-line; 2 -- parse or type errors; 3 -- compilation errors; 4 -- verification errors ---- Input configuration --------------------------------------------------- /stdin Read standard input and treat it as an input .dfy file. ---- Plugins --------------------------------------------------------------- ---- Overall reporting and printing ---------------------------------------- /stats Print interesting statistics about the Dafny files supplied. /printIncludes: None (default) - Print nothing. Immediate - Print files included by files listed on the command line. Transitive - Recurses on the files printed by Immediate. Immediate and Transitive will exit after printing. /view: Print the filtered views of a module after it is resolved (/rprint). If print before the module is resolved (/dprint), then everything in the module is printed. If no view is specified, then everything in the module is printed. /funcCallGraph Print out the function call graph. Format is: func,mod=callee* /pmtrace Print pattern-match compiler debug info. /printTooltips Dump additional positional information (displayed as mouse-over tooltips by the VS Code plugin) to stdout as 'Info' messages. /diagnosticsFormat: Choose how to report errors, warnings, and info messages. text (default) - Use human readable output json - Print each message as a JSON object, one per line. ---- Language feature selection -------------------------------------------- /noIncludes Ignore include directives. /noExterns Ignore extern attributes. /functionSyntax: The syntax for functions is changing from Dafny version 3 to version 4. This switch gives early access to the new syntax, and also provides a mode to help with migration. 3 - Compiled functions are written `function method` and `predicate method`. Ghost functions are written `function` and `predicate`. 4 (default) - Compiled functions are written `function` and `predicate`. Ghost functions are written `ghost function` and `ghost predicate`. migration3to4 - Compiled functions are written `function method` and `predicate method`. Ghost functions are written `ghost function` and `ghost predicate`. To migrate from version 3 to version 4, use this flag on your version 3 program. This will give flag all occurrences of `function` and `predicate` as parsing errors. These are ghost functions, so change those into the new syntax `ghost function` and `ghost predicate`. Then, start using /functionSyntax:4. This will flag all occurrences of `function method` and `predicate method` as parsing errors. So, change those to just `function` and `predicate`. Now, your program uses version 4 syntax and has the exact same meaning as your previous version 3 program. experimentalDefaultGhost - Like migration3to4, but allow `function` and `predicate` as alternatives to declaring ghost functions and predicates, respectively. experimentalDefaultCompiled - Like migration3to4, but allow `function` and `predicate` as alternatives to declaring compiled functions and predicates, respectively. experimentalPredicateAlwaysGhost - Compiled functions are written `function`. Ghost functions are written `ghost function`. Predicates are always ghost and are written `predicate`. /quantifierSyntax: The syntax for quantification domains is changing from Dafny version 3 to version 4, more specifically where quantifier ranges (| ) are allowed. This switch gives early access to the new syntax. 3 - Ranges are only allowed after all quantified variables are declared. (e.g. set x, y | 0 <= x < |s| && y in s[x] && 0 <= y :: y) 4 (default) - Ranges are allowed after each quantified variable declaration. (e.g. set x | 0 <= x < |s|, y <- s[x] | 0 <= y :: y) Note that quantifier variable domains (<- ) are available in both syntax versions. /disableScopes Treat all export sets as 'export reveal *'. i.e. don't hide function bodies or type definitions during translation. ---- Warning selection ----------------------------------------------------- /warnShadowing Emits a warning if the name of a declared variable caused another variable to be shadowed. /warnMissingConstructorParenthesis Emits a warning when a constructor name in a case pattern is not followed by parentheses. /deprecation: 0 - Don't give any warnings about deprecated features. 1 (default) - Show warnings about deprecated features. /warningsAsErrors Treat warnings as errors. ---- Verification options ------------------------------------------------- /dafnyVerify: 0 - Stop after resolution and typechecking. 1 - Continue on to verification and compilation. /verifyAllModules Verify modules that come from an include directive. /emitUncompilableCode Allow compilers to emit uncompilable code that usually contain useful information about what feature is missing, rather than stopping on the first problem /separateModuleOutput Output verification results for each module separately, rather than aggregating them after they are all finished. /noCheating: 0 (default) - Allow assume statements and free invariants. 1 - Treat all assumptions as asserts, and drop free. /induction: 0 - Never do induction, not even when attributes request it. 1 - Only apply induction when attributes request it. 2 - Apply induction as requested (by attributes) and also for heuristically chosen quantifiers. 3 - Apply induction as requested, and for heuristically chosen quantifiers and lemmas. 4 (default) - Apply induction as requested, and for lemmas. /inductionHeuristic: 0 - Least discriminating induction heuristic (that is, lean toward applying induction more often). 1,2,3,4,5 - Levels in between, ordered as follows as far as how discriminating they are: 0 < 1 < 2 < (3,4) < 5 < 6. 6 (default) - Most discriminating. /trackPrintEffects: 0 (default) - Every compiled method, constructor, and iterator, whether or not it bears a {{:print}} attribute, may have print effects. 1 - A compiled method, constructor, or iterator is allowed to have print effects only if it is marked with {{:print}}. /definiteAssignment: 0 - Ignores definite-assignment rules. This mode is for testing only--it is not sound. 1 (default) - Enforces definite-assignment rules for compiled variables and fields whose types do not support auto-initialization, and for ghost variables and fields whose type is possibly empty. 2 - Enforces definite-assignment for all non-yield-parameter variables and fields, regardless of their types. 3 - Like 2, but also performs checks in the compiler that no nondeterministic statements are used; thus, a program that passes at this level 3 is one that the language guarantees that values seen during execution will be the same in every run of the program. 4 - Like 1, but enforces definite assignment for all local variables and out-parameters, regardless of their types. (Whether or not fields and new arrays are subject to definite assignments depends on their types.) /noAutoReq Ignore autoReq attributes. /autoReqPrint: Print out requirements that were automatically generated by autoReq. /noNLarith Reduce Z3's knowledge of non-linear arithmetic (*,/,%). Results in more manual work, but also produces more predictable behavior. (This switch will perhaps be replaced by /arith in the future. For now, it takes precedence of /arith.) /arith: (experimental) Adjust how Dafny interprets arithmetic operations. 0 - Use Boogie/Z3 built-ins for all arithmetic operations. 1 (default) - Like 0, but introduce symbolic synonyms for *,/,%, and allow these operators to be used in triggers. 2 - Like 1, but introduce symbolic synonyms also for +,-. 3 - Turn off non-linear arithmetic in the SMT solver. Still, use Boogie/Z3 built-in symbols for all arithmetic operations. 4 - Like 3, but introduce symbolic synonyms for *,/,%, and allow these operators to be used in triggers. 5 - Like 4, but introduce symbolic synonyms also for +,-. 6 - Like 5, and introduce axioms that distribute + over *. 7 - like 6, and introduce facts that associate literals arguments of *. 8 - Like 7, and introduce axiom for the connection between *,/,%. 9 - Like 8, and introduce axioms for sign of multiplication. 10 - Like 9, and introduce axioms for commutativity and associativity of *. /autoTriggers: 0 - Do not generate {{:trigger}} annotations for user-level quantifiers. 1 (default) - Add a {{:trigger}} to each user-level quantifier. Existing annotations are preserved. /rewriteFocalPredicates: 0 - Don't rewrite predicates in the body of prefix lemmas. 1 (default) - In the body of prefix lemmas, rewrite any use of a focal predicate P to P#[_k-1]. /extractCounterexample If verification fails, report a detailed counterexample for the first failing assertion (experimental). ---- Compilation options --------------------------------------------------- /compile: 0 - Do not compile Dafny program. 1 (default) - Upon successful verification of the Dafny program, compile it to the designated target language. (/noVerify automatically counts as a failed verification.) 2 - Always attempt to compile Dafny program to the target language, regardless of verification outcome. 3 - If there is a Main method and there are no verification errors and /noVerify is not used, compiles program in memory (i.e., does not write an output file) and runs it. 4 - Like (3), but attempts to compile and run regardless of verification outcome. /Main: Specify the (fully-qualified) name of the method to use as the executable entry point. Default is the method with the {{:main}} attribute, or else the method named 'Main'. A Main method can have at most one (non-ghost) argument of type `seq` --args ... When running a Dafny file through /compile:3 or /compile:4, '--args' provides all arguments after it to the Main function, at index starting at 1. Index 0 is used to store the executable's name if it exists. /compileVerbose: 0 - Don't print status of compilation to the console. 1 (default) - Print information such as files being written by the compiler to the console. /spillTargetCode: Explicitly writes the code in the target language to one or more files. This is not necessary to run a Dafny program, but may be of interest when building multi-language programs or for debugging. 0 (default) - Don't make any extra effort to write the textual target program (but still compile it, if /compile indicates to do so). 1 - Write the textual target program, if it is being compiled. 2 - Write the textual target program, provided it passes the verifier (and /noVerify is NOT used), regardless of /compile setting. 3 - Write the textual target program, regardless of verification outcome and /compile setting. Note, some compiler targets may (always or in some situations) write out the textual target program as part of compilation, in which case /spillTargetCode:0 behaves the same way as /spillTargetCode:1. /coverage: The compiler emits branch-coverage calls and outputs into a legend that gives a description of each source-location identifier used in the branch-coverage calls. (Use - as to print to the console.) /optimize Produce optimized C# code by passing the /optimize flag to csc.exe. /optimizeResolution: 0 - Resolve and translate all methods. 1 - Translate methods only in the call graph of current verification target. 2 (default) - As in 1, but only resolve method bodies in non-included Dafny sources. /useRuntimeLib Refer to a pre-built DafnyRuntime.dll in the compiled assembly rather than including DafnyRuntime.cs verbatim. /testContracts: Enable run-time testing of the compilable portions of certain function or method contracts, at their call sites. The current implementation focuses on {{:extern}} code but may support other code in the future. Externs - Check contracts on every call to a function or method marked with the {{:extern}} attribute, regardless of where it occurs. TestedExterns - Check contracts on every call to a function or method marked with the {{:extern}} attribute when it occurs in a method with the {{:test}} attribute, and warn if no corresponding test exists for a given external declaration. ---------------------------------------------------------------------------- Dafny generally accepts Boogie options and passes these on to Boogie. However, some Boogie options, like /loopUnroll, may not be sound for Dafny or may not have the same meaning for a Dafny program as it would for a similar Boogie program. ".Replace("\n", "\n "); } } class ErrorReportingCommandLineParseState : Bpl.CommandLineParseState { private readonly Errors errors; private IOrigin token; public ErrorReportingCommandLineParseState(string[] args, string toolName, Errors errors, IOrigin token) : base(args, toolName) { this.errors = errors; this.token = token; } public override void Error(string message, params string[] args) { errors.SemErr(GenericErrors.ErrorId.g_option_error, token, string.Format(message, args)); EncounteredErrors = true; } } /// /// Wrapper object that restricts which options may be applied. /// Used by the parser to parse :options strings. /// class DafnyAttributeOptions : DafnyOptions { public static readonly HashSet KnownOptions = [ "functionSyntax", "quantifierSyntax" ]; private readonly Errors errors; public IOrigin Token { get; set; } public DafnyAttributeOptions(DafnyOptions opts, Errors errors) : base(opts) { this.errors = errors; Token = null; } protected override Bpl.CommandLineParseState InitializeCommandLineParseState(string[] args) { return new ErrorReportingCommandLineParseState(args, ToolName, errors, Token ?? Microsoft.Dafny.Token.NoToken); } private void Unsupported(string name, Bpl.CommandLineParseState ps) { ps.Error($"Option {name} unrecognized or unsupported in ':options' attributes."); } protected override void UnknownSwitch(Bpl.CommandLineParseState ps) { Unsupported(ps.s, ps); } protected override bool ParseOption(string name, Bpl.CommandLineParseState ps) { if (!KnownOptions.Contains(name)) { return false; } return base.ParseOption(name, ps); } protected override void AddFile(string file, Bpl.CommandLineParseState ps) { Unsupported(file, ps); } }