diff --git a/.github/workflows/check-deep-tests-reusable.yml b/.github/workflows/check-deep-tests-reusable.yml index 72996ae803..eb38ecebc9 100644 --- a/.github/workflows/check-deep-tests-reusable.yml +++ b/.github/workflows/check-deep-tests-reusable.yml @@ -1,6 +1,7 @@ name: Check Deep Tests (Reusable Workflow) on: + workflow_dispatch: workflow_call: jobs: diff --git a/.github/workflows/check-for-workflow-run.js b/.github/workflows/check-for-workflow-run.js index 1b0b3d8490..ced1918289 100644 --- a/.github/workflows/check-for-workflow-run.js +++ b/.github/workflows/check-for-workflow-run.js @@ -16,13 +16,18 @@ module.exports = async ({github, context, core, workflow_id, sha, ...config}) => const runFilterDesc = sha ? `${workflow_id} on ${sha}` : workflow_id for (const run of result.data.workflow_runs) { if ((!sha || run.head_sha === sha)) { - if (run.conclusion === "success") { - // The SHA is fully tested, exit with success - console.log(`Last run of ${runFilterDesc} succeeded: ${run.html_url}`) - return - } else if (run.status === "failure" || run.status === "timed_out") { - core.setFailed(`Last run of ${runFilterDesc} did not succeed: ${run.html_url}`) - return + // The status property can be one of: “queued”, “in_progress”, or “completed”. + if (run.status === "completed") { + // The conclusion property can be one of: + // “success”, “failure”, “neutral”, “cancelled”, “skipped”, “timed_out”, or “action_required”. + if (run.conclusion === "success") { + // The SHA is fully tested, exit with success + console.log(`Last run of ${runFilterDesc} succeeded: ${run.html_url}`) + return + } else if (run.conclusion === "failure" || run.conclusion === "timed_out") { + core.setFailed(`Last run of ${runFilterDesc} did not succeed: ${run.html_url}`) + return + } } } } diff --git a/Source/DafnyCore/AST/Grammar/IndentationFormatter.cs b/Source/DafnyCore/AST/Grammar/IndentationFormatter.cs index 0256db2fb2..e2e83a3a0a 100644 --- a/Source/DafnyCore/AST/Grammar/IndentationFormatter.cs +++ b/Source/DafnyCore/AST/Grammar/IndentationFormatter.cs @@ -23,11 +23,15 @@ public IndentationFormatter(TokenNewIndentCollector formatter) { /// Creates an IndentationFormatter for the given program, /// by immediately processing all nodes and assigning indentations to most structural tokens /// - public static IndentationFormatter ForProgram(Program program, bool reduceBlockiness = true) { - var tokenNewIndentCollector = new TokenNewIndentCollector(program) { + public static IndentationFormatter ForProgram(Program program, Uri fileToFormat, bool reduceBlockiness = true) { + var tokenNewIndentCollector = new TokenNewIndentCollector(program, fileToFormat) { ReduceBlockiness = reduceBlockiness }; foreach (var child in program.DefaultModuleDef.PreResolveChildren) { + var isPhysicalToken = child.Tok.line != 0; + if (isPhysicalToken && child.Tok.Uri != fileToFormat) { + continue; + } if (child is TopLevelDecl topLevelDecl) { tokenNewIndentCollector.SetDeclIndentation(topLevelDecl, 0); } else if (child is Include include) { diff --git a/Source/DafnyCore/AST/Grammar/TokenNewIndentCollector.cs b/Source/DafnyCore/AST/Grammar/TokenNewIndentCollector.cs index ca0597ec55..0a2c218065 100644 --- a/Source/DafnyCore/AST/Grammar/TokenNewIndentCollector.cs +++ b/Source/DafnyCore/AST/Grammar/TokenNewIndentCollector.cs @@ -83,8 +83,9 @@ private Indentations PosToIndentations(int pos) { public int binOpIndent = -1; public int binOpArgIndent = -1; - internal TokenNewIndentCollector(Program program) { + internal TokenNewIndentCollector(Program program, Uri fileToFormat) { this.program = program; + this.fileToFormat = fileToFormat; preResolve = true; } @@ -222,6 +223,7 @@ public int GetRightAlignIndentAfter(IToken token, int indentFallback) { } private static readonly Regex FollowedByNewlineRegex = new Regex("^[ \t]*([\r\n]|//)"); + private readonly Uri fileToFormat; public static bool IsFollowedByNewline(IToken token) { return FollowedByNewlineRegex.IsMatch(token.TrailingTrivia); @@ -231,7 +233,7 @@ public static bool IsFollowedByNewline(IToken token) { // 'inline' is the hypothetical indentation of this token if it was on its own line // 'below' is the hypothetical indentation of a comment after that token, and of the next token if it does not have a set indentation public void SetIndentations(IToken token, int above = -1, int inline = -1, int below = -1) { - if (token.FromIncludeDirective(program) || (token.line == 0 && token.col == 0)) { + if (token.Uri != fileToFormat || (token.line == 0 && token.col == 0)) { // Just ignore this token. return; } diff --git a/Source/DafnyCore/AST/Members/ICodeContext.cs b/Source/DafnyCore/AST/Members/ICodeContext.cs index 21b2959b1e..3515cb202a 100644 --- a/Source/DafnyCore/AST/Members/ICodeContext.cs +++ b/Source/DafnyCore/AST/Members/ICodeContext.cs @@ -170,4 +170,6 @@ public interface RedirectingTypeDecl : ICallable { SubsetTypeDecl.WKind WitnessKind { get; } Expression/*?*/ Witness { get; } // non-null iff WitnessKind is Compiled or Ghost FreshIdGenerator IdGenerator { get; } + + [FilledInDuringResolution] bool ConstraintIsCompilable { get; set; } } \ No newline at end of file diff --git a/Source/DafnyCore/AST/SystemModuleManager.cs b/Source/DafnyCore/AST/SystemModuleManager.cs index 215d2439b4..8455b0f63e 100644 --- a/Source/DafnyCore/AST/SystemModuleManager.cs +++ b/Source/DafnyCore/AST/SystemModuleManager.cs @@ -103,6 +103,7 @@ public SystemModuleManager(DafnyOptions options) { NatDecl = new SubsetTypeDecl(RangeToken.NoToken, new Name("nat"), new TypeParameter.TypeParameterCharacteristics(TypeParameter.EqualitySupportValue.InferredRequired, Type.AutoInitInfo.CompilableValue, false), new List(), SystemModule, bvNat, natConstraint, SubsetTypeDecl.WKind.CompiledZero, null, ax); + ((RedirectingTypeDecl)NatDecl).ConstraintIsCompilable = true; SystemModule.SourceDecls.Add(NatDecl); // create trait 'object' ObjectDecl = new TraitDecl(RangeToken.NoToken, new Name("object"), SystemModule, new List(), new List(), DontCompile(), false, null); diff --git a/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs b/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs index abd9b684fd..ce41305f19 100644 --- a/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs +++ b/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs @@ -15,6 +15,12 @@ public class NewtypeDecl : TopLevelDeclWithMembers, RevealableTypeDecl, Redirect public SubsetTypeDecl.WKind WitnessKind { get; set; } = SubsetTypeDecl.WKind.CompiledZero; public Expression/*?*/ Witness { get; set; } // non-null iff WitnessKind is Compiled or Ghost [FilledInDuringResolution] public NativeType NativeType; // non-null for fixed-size representations (otherwise, use BigIntegers for integers) + + [FilledInDuringResolution] bool RedirectingTypeDecl.ConstraintIsCompilable { get; set; } + + [FilledInDuringResolution] public bool NativeTypeRangeImpliesAllConstraints; // indicates that all values in the range of the native type are values of the newtype + [FilledInDuringResolution] public bool TargetTypeCoversAllBitPatterns; // "target complete" -- indicates that any bit pattern that can fill the target type is a value of the newtype + public NewtypeDecl(RangeToken rangeToken, Name name, ModuleDefinition module, Type baseType, List parentTraits, List members, Attributes attributes, bool isRefining) : base(rangeToken, name, module, new List(), members, attributes, isRefining, parentTraits) { @@ -24,6 +30,7 @@ public NewtypeDecl(RangeToken rangeToken, Name name, ModuleDefinition module, Ty Contract.Requires(isRefining ^ (baseType != null)); Contract.Requires(members != null); BaseType = baseType; + this.NewSelfSynonym(); } public NewtypeDecl(RangeToken rangeToken, Name name, ModuleDefinition module, BoundVar bv, Expression constraint, SubsetTypeDecl.WKind witnessKind, Expression witness, List parentTraits, List members, Attributes attributes, bool isRefining) diff --git a/Source/DafnyCore/AST/TypeDeclarations/SubsetTypeDecl.cs b/Source/DafnyCore/AST/TypeDeclarations/SubsetTypeDecl.cs index a7549b338a..8d235f2708 100644 --- a/Source/DafnyCore/AST/TypeDeclarations/SubsetTypeDecl.cs +++ b/Source/DafnyCore/AST/TypeDeclarations/SubsetTypeDecl.cs @@ -11,8 +11,10 @@ public class SubsetTypeDecl : TypeSynonymDecl, RedirectingTypeDecl, ICanAutoReve public enum WKind { CompiledZero, Compiled, Ghost, OptOut, Special } public readonly WKind WitnessKind; public Expression/*?*/ Witness; // non-null iff WitnessKind is Compiled or Ghost - [FilledInDuringResolution] public bool ConstraintIsCompilable; [FilledInDuringResolution] public bool CheckedIfConstraintIsCompilable = false; // Set to true lazily by the Resolver when the Resolver fills in "ConstraintIsCompilable". + + [FilledInDuringResolution] bool RedirectingTypeDecl.ConstraintIsCompilable { get; set; } + public SubsetTypeDecl(RangeToken rangeToken, Name name, TypeParameter.TypeParameterCharacteristics characteristics, List typeArgs, ModuleDefinition module, BoundVar id, Expression constraint, WKind witnessKind, Expression witness, Attributes attributes) diff --git a/Source/DafnyCore/AST/TypeDeclarations/TypeSynonymDeclBase.cs b/Source/DafnyCore/AST/TypeDeclarations/TypeSynonymDeclBase.cs index 4017f3bdad..308b7f542f 100644 --- a/Source/DafnyCore/AST/TypeDeclarations/TypeSynonymDeclBase.cs +++ b/Source/DafnyCore/AST/TypeDeclarations/TypeSynonymDeclBase.cs @@ -64,6 +64,12 @@ public Type RhsWithArgumentIgnoringScope(List typeArgs) { ModuleDefinition RedirectingTypeDecl.Module { get { return EnclosingModuleDefinition; } } BoundVar RedirectingTypeDecl.Var { get { return null; } } Expression RedirectingTypeDecl.Constraint { get { return null; } } + + bool RedirectingTypeDecl.ConstraintIsCompilable { + get => false; + set => throw new NotSupportedException(); + } + SubsetTypeDecl.WKind RedirectingTypeDecl.WitnessKind { get { return SubsetTypeDecl.WKind.CompiledZero; } } Expression RedirectingTypeDecl.Witness { get { return null; } } FreshIdGenerator RedirectingTypeDecl.IdGenerator { get { return IdGenerator; } } diff --git a/Source/DafnyCore/Compilers/CSharp/Compiler-Csharp.cs b/Source/DafnyCore/Compilers/CSharp/Compiler-Csharp.cs index 61a7c3b705..f5c283f664 100644 --- a/Source/DafnyCore/Compilers/CSharp/Compiler-Csharp.cs +++ b/Source/DafnyCore/Compilers/CSharp/Compiler-Csharp.cs @@ -3291,6 +3291,8 @@ protected override void EmitConversionExpr(ConversionExpr e, bool inLetExprBody, } else { Contract.Assert(false, $"not implemented for C#: {e.E.Type} -> {e.ToType}"); } + } else if (e.E.Type.Equals(e.ToType) || e.E.Type.AsNewtype != null || e.ToType.AsNewtype != null) { + wr.Append(Expr(e.E, inLetExprBody, wStmts)); } else { Contract.Assert(false, $"not implemented for C#: {e.E.Type} -> {e.ToType}"); } diff --git a/Source/DafnyCore/Compilers/Cplusplus/Compiler-cpp.cs b/Source/DafnyCore/Compilers/Cplusplus/Compiler-cpp.cs index 7f7b64650c..1db454938d 100644 --- a/Source/DafnyCore/Compilers/Cplusplus/Compiler-cpp.cs +++ b/Source/DafnyCore/Compilers/Cplusplus/Compiler-cpp.cs @@ -2356,11 +2356,14 @@ protected override void EmitConversionExpr(ConversionExpr e, bool inLetExprBody, wr.Write(".{0}()", Capitalize(GetNativeTypeName(nt))); } } - } else { - Contract.Assert(e.E.Type.IsBigOrdinalType); + } else if (e.E.Type.IsBigOrdinalType) { Contract.Assert(e.ToType.IsNumericBased(Type.NumericPersuasion.Int)); // identity will do wr.Append(Expr(e.E, inLetExprBody, wStmts)); + } else { + // identity will do + Contract.Assert(e.E.Type.Equals(e.ToType) || e.E.Type.AsNewtype != null || e.ToType.AsNewtype != null); + wr.Append(Expr(e.E, inLetExprBody, wStmts)); } } diff --git a/Source/DafnyCore/Compilers/GoLang/Compiler-go.cs b/Source/DafnyCore/Compilers/GoLang/Compiler-go.cs index ea506f6cae..359434bd4c 100644 --- a/Source/DafnyCore/Compilers/GoLang/Compiler-go.cs +++ b/Source/DafnyCore/Compilers/GoLang/Compiler-go.cs @@ -3576,6 +3576,8 @@ protected override void EmitConversionExpr(ConversionExpr e, bool inLetExprBody, wr.Write(".{0}()", Capitalize(GetNativeTypeName(nt))); } } + } else if (e.E.Type.Equals(e.ToType) || e.E.Type.AsNewtype != null || e.ToType.AsNewtype != null) { + wr.Append(Expr(e.E, inLetExprBody, wStmts)); } else { Contract.Assert(false, $"not implemented for go: {e.E.Type} -> {e.ToType}"); } diff --git a/Source/DafnyCore/Compilers/Java/Compiler-java.cs b/Source/DafnyCore/Compilers/Java/Compiler-java.cs index face2a1e8d..d2c7254b86 100644 --- a/Source/DafnyCore/Compilers/Java/Compiler-java.cs +++ b/Source/DafnyCore/Compilers/Java/Compiler-java.cs @@ -4186,6 +4186,8 @@ protected override void EmitConversionExpr(ConversionExpr e, bool inLetExprBody, } else { Contract.Assert(false, $"not implemented for java: {fromType} -> {toType}"); } + } else if (e.E.Type.Equals(e.ToType) || e.E.Type.AsNewtype != null || e.ToType.AsNewtype != null) { + wr.Append(Expr(e.E, inLetExprBody, wStmts)); } else { Contract.Assert(false, $"not implemented for java: {fromType} -> {toType}"); } diff --git a/Source/DafnyCore/Compilers/JavaScript/Compiler-js.cs b/Source/DafnyCore/Compilers/JavaScript/Compiler-js.cs index 249c8395a6..fc31dc63a2 100644 --- a/Source/DafnyCore/Compilers/JavaScript/Compiler-js.cs +++ b/Source/DafnyCore/Compilers/JavaScript/Compiler-js.cs @@ -2432,6 +2432,8 @@ protected override void EmitConversionExpr(ConversionExpr e, bool inLetExprBody, if (e.ToType.IsCharType) { wr.Write(").toNumber())"); } + } else if (e.E.Type.Equals(e.ToType) || e.E.Type.AsNewtype != null || e.ToType.AsNewtype != null) { + wr.Append(Expr(e.E, inLetExprBody, wStmts)); } else { Contract.Assert(false, $"not implemented for javascript: {e.E.Type} -> {e.ToType}"); } diff --git a/Source/DafnyCore/Compilers/Python/PythonBackend.cs b/Source/DafnyCore/Compilers/Python/PythonBackend.cs index 41189692bb..247f858e9a 100644 --- a/Source/DafnyCore/Compilers/Python/PythonBackend.cs +++ b/Source/DafnyCore/Compilers/Python/PythonBackend.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Runtime.InteropServices; namespace Microsoft.Dafny.Compilers; @@ -46,6 +47,17 @@ private static string FindModuleName(string externFilename) { return Path.GetExtension(externFilename) == ".py" ? Path.GetFileNameWithoutExtension(externFilename) : null; } + private static readonly Dictionary PlatformDefaults = new() { + {OSPlatform.Linux, "python3"}, + {OSPlatform.Windows, "python"}, + {OSPlatform.FreeBSD, "python3"}, + {OSPlatform.OSX, "python3"}, + }; + private static string DefaultPythonCommand => PlatformDefaults.SingleOrDefault( + kv => RuntimeInformation.IsOSPlatform(kv.Key), + new(OSPlatform.Linux, "python3") + ).Value; + bool CopyExternLibraryIntoPlace(string externFilename, string mainProgram, TextWriter outputWriter) { // Grossly, we need to look in the file to figure out where to put it var moduleName = FindModuleName(externFilename); @@ -88,7 +100,7 @@ public override bool CompileTargetProgram(string dafnyProgramName, string target } } if (!runAfterCompile) { - var psi = PrepareProcessStartInfo("python3"); + var psi = PrepareProcessStartInfo(DefaultPythonCommand); psi.Arguments = $"-m compileall -q {Path.GetDirectoryName(targetFilename)}"; return 0 == RunProcess(psi, outputWriter, outputWriter, "Error while compiling Python files."); } @@ -99,7 +111,7 @@ public override bool RunTargetProgram(string dafnyProgramName, string targetProg string targetFilename, ReadOnlyCollection otherFileNames, object compilationResult, TextWriter outputWriter, TextWriter errorWriter) { Contract.Requires(targetFilename != null || otherFileNames.Count == 0); - var psi = PrepareProcessStartInfo("python3", Options.MainArgs.Prepend(targetFilename)); + var psi = PrepareProcessStartInfo(DefaultPythonCommand, Options.MainArgs.Prepend(targetFilename)); psi.EnvironmentVariables["PYTHONIOENCODING"] = "utf8"; return 0 == RunProcess(psi, outputWriter, errorWriter); } diff --git a/Source/DafnyCore/Compilers/SinglePassCompiler.cs b/Source/DafnyCore/Compilers/SinglePassCompiler.cs index 0e7a3d1527..82673af45f 100644 --- a/Source/DafnyCore/Compilers/SinglePassCompiler.cs +++ b/Source/DafnyCore/Compilers/SinglePassCompiler.cs @@ -669,10 +669,10 @@ private ConcreteSyntaxTree CreateGuardedForeachLoop( ) { wr = CreateForeachLoop(tmpVarName, collectionElementType, tok, out var collectionWriter, wr); collection(collectionWriter); - wr = MaybeInjectSubtypeConstraint(tmpVarName, collectionElementType, boundVar.Type, inLetExprBody, tok, wr); + wr = MaybeInjectSubtypeConstraintWrtTraits(tmpVarName, collectionElementType, boundVar.Type, inLetExprBody, tok, wr); EmitDowncastVariableAssignment(IdName(boundVar), boundVar.Type, tmpVarName, collectionElementType, introduceBoundVar, tok, wr); - wr = MaybeInjectSubsetConstraint(boundVar, boundVar.Type, collectionElementType, inLetExprBody, tok, wr); + wr = MaybeInjectSubsetConstraint(boundVar, boundVar.Type, inLetExprBody, tok, wr); return wr; } @@ -686,7 +686,7 @@ protected abstract Action GetSubtypeCondition( string tmpVarName, Type boundVarType, IToken tok, ConcreteSyntaxTree wPreconditions); /// - /// Emit an (already verified) downcast assignment like: + /// Emit an upcast or (already verified) downcast assignment like: /// /// var boundVarName:boundVarType := tmpVarName as boundVarType; /// [[bodyWriter]] @@ -1410,6 +1410,13 @@ protected abstract void EmitMapDisplay(MapType mt, IToken tok, List protected abstract string GetCollectionBuilder_Build(CollectionType ct, IToken tok, string collName, ConcreteSyntaxTree wr); + /// + /// Returns a pair (ty, f) where + /// * "f" is a closure that, to a given writer, emits code that enumerates an integer-valued range from + /// "wLo" to "wHi" using the target type of "type" + /// * "ty" is a Dafny type whose target type is the same as the target type of "type" + /// It is assumed that "type" is some integer-based type (not a bitvector type, for example). + /// protected virtual (Type, Action) EmitIntegerRange(Type type, Action wLo, Action wHi) { Type result; if (AsNativeType(type) != null) { @@ -3776,6 +3783,12 @@ protected virtual ConcreteSyntaxTree EmitAnd(Action lhs, Con return wr; } + /// + /// Returns a type whose target type is the same as the target type of the values returned by the emitted enumeration. + /// The output value of "collectionWriter" is an action that emits the enumeration. + /// Note that, while the values returned bny the enumeration have the target representation of "bv.Type", they may + /// not be legal "bv.Type" values -- that is, it could be that "bv.Type" has further constraints that need to be checked. + /// Type CompileCollection(ComprehensionExpr.BoundedPool bound, IVariable bv, bool inLetExprBody, bool includeDuplicates, Substituter/*?*/ su, out Action collectionWriter, ConcreteSyntaxTree wStmts, List/*?*/ bounds = null, List/*?*/ boundVars = null, int boundIndex = 0) { @@ -5527,13 +5540,13 @@ void EmitExpr(Expression e2, ConcreteSyntaxTree wr2, bool inLetExpr, ConcreteSyn var tmpVarName = ProtectedFreshId(e is ForallExpr ? "_forall_var_" : "_exists_var_"); ConcreteSyntaxTree newWBody = CreateLambda(new List { collectionElementType }, e.tok, new List { tmpVarName }, Type.Bool, wBody, wStmts, untyped: true); wStmts = newWBody.Fork(); - newWBody = MaybeInjectSubtypeConstraint( + newWBody = MaybeInjectSubtypeConstraintWrtTraits( tmpVarName, collectionElementType, bv.Type, inLetExprBody, e.tok, newWBody, true, e is ForallExpr); EmitDowncastVariableAssignment( IdName(bv), bv.Type, tmpVarName, collectionElementType, true, e.tok, newWBody); newWBody = MaybeInjectSubsetConstraint( - bv, bv.Type, collectionElementType, inLetExprBody, e.tok, newWBody, true, e is ForallExpr); + bv, bv.Type, inLetExprBody, e.tok, newWBody, isReturning: true, elseReturnValue: e is ForallExpr); wBody = newWBody; } EmitExpr(logicalBody, inLetExprBody, wBody, wStmts); @@ -5698,7 +5711,7 @@ protected void WriteTypeDescriptors(TopLevelDecl decl, List typeArguments, /// ... /// } /// - /// MaybeInjectSubtypeConstraint emits a subtype constraint that tmpVarName should be of type boundVarType, typically of the form + /// MaybeInjectSubtypeConstraintWrtTraits emits a subtype constraint that tmpVarName should be of type boundVarType, typically of the form /// /// if([tmpVarName] is [boundVarType]) { /// // This is where 'wr' will write @@ -5708,19 +5721,21 @@ protected void WriteTypeDescriptors(TopLevelDecl decl, List typeArguments, /// to use in the lambdas used by forall and exists statements: /// /// if([tmpVarName] is [boundVarType]) { - /// return // This is where 'wr' will write + /// // This is where 'wr' will write /// } else { /// return [elseReturnValue]; /// } /// /// - /// - private ConcreteSyntaxTree MaybeInjectSubtypeConstraint(string tmpVarName, - Type collectionElementType, Type boundVarType, bool inLetExprBody, - IToken tok, ConcreteSyntaxTree wr, bool isReturning = false, bool elseReturnValue = false - ) { - var iterationValuesNeedToBeChecked = IsTargetSupertype(collectionElementType, boundVarType); - if (iterationValuesNeedToBeChecked) { + private ConcreteSyntaxTree MaybeInjectSubtypeConstraintWrtTraits(string tmpVarName, Type collectionElementType, Type boundVarType, + bool inLetExprBody, IToken tok, ConcreteSyntaxTree wr, + bool isReturning = false, bool elseReturnValue = false) { + + if (Type.IsSupertype(boundVarType, collectionElementType)) { + // Every value of the collection enumeration is a value of the bound variable's type, so the assignment can be done unconditionally. + // (The caller may still need to insert an upcast, depending on the target language.) + } else { + // We need to perform a run-time check to see if the collection value can be assigned to the bound variable. var preconditions = wr.Fork(); var conditions = GetSubtypeCondition(tmpVarName, boundVarType, tok, preconditions); if (conditions == null) { @@ -5732,9 +5747,7 @@ private ConcreteSyntaxTree MaybeInjectSubtypeConstraint(string tmpVarName, wr = EmitBlock(wr); var wStmts = wr.Fork(); wr = EmitReturnExpr(wr); - var elseLiteral = new LiteralExpr(tok, elseReturnValue) { - Type = Type.Bool - }; + var elseLiteral = Expression.CreateBoolLiteral(tok, elseReturnValue); EmitExpr(elseLiteral, inLetExprBody, wr, wStmts); } wr = thenWriter; @@ -5743,50 +5756,48 @@ private ConcreteSyntaxTree MaybeInjectSubtypeConstraint(string tmpVarName, return wr; } + /// + /// If needed, emit an if-statement wrapper that checks that the value stored in "boundVar" satisfies any (subset-type or newtype) constraints + /// of "boundVarType". + /// private ConcreteSyntaxTree MaybeInjectSubsetConstraint(IVariable boundVar, Type boundVarType, - Type collectionElementType, bool inLetExprBody, - IToken tok, ConcreteSyntaxTree wr, bool isReturning = false, bool elseReturnValue = false, - bool isSubfiltering = false) { - if (!boundVarType.Equals(collectionElementType, true) && - boundVarType.NormalizeExpand(true) is UserDefinedType { - TypeArgs: var typeArgs, - ResolvedClass: - SubsetTypeDecl { - TypeArgs: var typeParametersArgs, - Var: var variable, - Constraint: var constraint + bool inLetExprBody, IToken tok, ConcreteSyntaxTree wr, + bool isReturning = false, bool elseReturnValue = false, bool isSubfiltering = false) { + + if (boundVarType.NormalizeExpandKeepConstraints() is UserDefinedType { ResolvedClass: RedirectingTypeDecl and var declWithConstraint } udt) { + if (declWithConstraint is SubsetTypeDecl or NewtypeDecl { NativeTypeRangeImpliesAllConstraints: false }) { + // the type is a subset type or newtype with non-trivial constraints + + var baseType = (declWithConstraint.Var?.Type ?? ((NewtypeDecl)declWithConstraint).BaseType).NormalizeExpandKeepConstraints(); + if (baseType is UserDefinedType { ResolvedClass: SubsetTypeDecl or NewtypeDecl } normalizedVariableType) { + wr = MaybeInjectSubsetConstraint(boundVar, normalizedVariableType, + inLetExprBody, tok, wr, isReturning: isReturning, elseReturnValue: elseReturnValue, isSubfiltering: true); + } + + if (declWithConstraint.Var != null) { + var typeMap = TypeParameter.SubstitutionMap(declWithConstraint.TypeArgs, udt.TypeArgs); + var instantiatedBaseType = baseType.Subst(typeMap); + var theValue = new ConversionExpr(tok, new IdentifierExpr(tok, boundVar), instantiatedBaseType) { Type = instantiatedBaseType }; + var subContract = new Substituter(null, + new Dictionary() { + {declWithConstraint.Var, theValue} + }, + typeMap + ); + var constraintInContext = subContract.Substitute(declWithConstraint.Constraint); + var wStmts = wr.Fork(); + var thenWriter = EmitIf(out var guardWriter, hasElse: isReturning, wr); + EmitExpr(constraintInContext, inLetExprBody, guardWriter, wStmts); + if (isReturning) { + var elseBranch = wr; + elseBranch = EmitBlock(elseBranch); + elseBranch = EmitReturnExpr(elseBranch); + wStmts = elseBranch.Fork(); + EmitExpr(new LiteralExpr(tok, elseReturnValue), inLetExprBody, elseBranch, wStmts); } - }) { - if (variable.Type.NormalizeExpandKeepConstraints() is UserDefinedType { - ResolvedClass: SubsetTypeDecl - } normalizedVariableType) { - wr = MaybeInjectSubsetConstraint(boundVar, normalizedVariableType, collectionElementType, - inLetExprBody, tok, wr, isReturning, elseReturnValue, true); - } - - var bvIdentifier = new IdentifierExpr(tok, boundVar); - var typeParameters = TypeParameter.SubstitutionMap(typeParametersArgs, typeArgs); - var subContract = new Substituter(null, - new Dictionary() - { - {variable, bvIdentifier} - }, - new Dictionary( - typeParameters - ) - ); - var constraintInContext = subContract.Substitute(constraint); - var wStmts = wr.Fork(); - var thenWriter = EmitIf(out var guardWriter, hasElse: isReturning, wr); - EmitExpr(constraintInContext, inLetExprBody, guardWriter, wStmts); - if (isReturning) { - var elseBranch = wr; - elseBranch = EmitBlock(elseBranch); - elseBranch = EmitReturnExpr(elseBranch); - wStmts = elseBranch.Fork(); - EmitExpr(new LiteralExpr(tok, elseReturnValue), inLetExprBody, elseBranch, wStmts); - } - wr = thenWriter; + wr = thenWriter; + } + } } if (isReturning && !isSubfiltering) { diff --git a/Source/DafnyCore/DafnyConsolePrinter.cs b/Source/DafnyCore/DafnyConsolePrinter.cs index 36bb9f2151..8a22e60103 100644 --- a/Source/DafnyCore/DafnyConsolePrinter.cs +++ b/Source/DafnyCore/DafnyConsolePrinter.cs @@ -69,7 +69,7 @@ private string GetFileLine(Uri uri, int lineIndex) { // Note: This is not guaranteed to be the same file that Dafny parsed. To ensure that, Dafny should keep // an in-memory version of each file it parses. var file = DafnyFile.CreateAndValidate(new ErrorReporterSink(options), OnDiskFileSystem.Instance, options, uri, Token.NoToken); - var reader = file.GetContent(); + using var reader = file.GetContent(); lines = Util.Lines(reader).ToList(); } catch (Exception) { lines = new List(); diff --git a/Source/DafnyCore/Resolver/BoundsDiscovery.cs b/Source/DafnyCore/Resolver/BoundsDiscovery.cs index f7ccac387f..8ac1512104 100644 --- a/Source/DafnyCore/Resolver/BoundsDiscovery.cs +++ b/Source/DafnyCore/Resolver/BoundsDiscovery.cs @@ -303,7 +303,7 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte // Note, in the following loop, it's important to go backwards, because DiscoverAllBounds_Aux_SingleVar assumes "knownBounds" has been // filled in for higher-indexed variables. for (var j = bvars.Count; 0 <= --j;) { - var bounds = DiscoverAllBounds_Aux_SingleVar(bvars, j, expr, polarity, knownBounds); + var bounds = DiscoverAllBounds_Aux_SingleVar(bvars, j, expr, polarity, knownBounds, out _); knownBounds[j] = ComprehensionExpr.BoundedPool.GetBest(bounds); #if DEBUG_PRINT if (knownBounds[j] is ComprehensionExpr.IntBoundedPool) { @@ -321,9 +321,11 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte return knownBounds; } - public static List DiscoverAllBounds_SingleVar(VT v, Expression expr) where VT : IVariable { + public static List DiscoverAllBounds_SingleVar(VT v, Expression expr, + out bool constraintConsistsSolelyOfRangeConstraints) where VT : IVariable { expr = Expression.CreateAnd(GetImpliedTypeConstraint(v, v.Type), expr); - return DiscoverAllBounds_Aux_SingleVar(new List { v }, 0, expr, true, new List() { null }); + return DiscoverAllBounds_Aux_SingleVar(new List { v }, 0, expr, true, + new List() { null }, out constraintConsistsSolelyOfRangeConstraints); } /// @@ -331,7 +333,7 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte /// that is not bounded. /// private static List DiscoverAllBounds_Aux_SingleVar(List bvars, int j, Expression expr, - bool polarity, List knownBounds) where VT : IVariable { + bool polarity, List knownBounds, out bool constraintConsistsSolelyOfRangeConstraints) where VT : IVariable { Contract.Requires(bvars != null); Contract.Requires(0 <= j && j < bvars.Count); Contract.Requires(expr != null); @@ -354,7 +356,14 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte } // Go through the conjuncts of the range expression to look for bounds. + // Only very specific conjuncts qualify for "constraintConsistsSolelyOfRangeConstraints". To make the bookkeeping + // of these simple, we count the total number of conjuncts and the number of qualifying conjuncts. Then, after the + // loop, we can compare these. + var totalNumberOfConjuncts = 0; + var conjunctsQualifyingAsRangeConstraints = 0; foreach (var conjunct in NormalizedConjuncts(expr, polarity)) { + totalNumberOfConjuncts++; + if (conjunct is IdentifierExpr) { var ide = (IdentifierExpr)conjunct; if (ide.Var == (IVariable)bv) { @@ -426,6 +435,7 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte case BinaryExpr.ResolvedOpcode.SeqEq: case BinaryExpr.ResolvedOpcode.MultiSetEq: case BinaryExpr.ResolvedOpcode.MapEq: + conjunctsQualifyingAsRangeConstraints++; var otherOperand = whereIsBv == 0 ? e1 : e0; bounds.Add(new ComprehensionExpr.ExactBoundedPool(otherOperand)); break; @@ -435,6 +445,7 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte throw new cce.UnreachableException(); // promised by postconditions of NormalizedConjunct case BinaryExpr.ResolvedOpcode.Lt: if (e0.Type.IsNumericBased(Type.NumericPersuasion.Int)) { + conjunctsQualifyingAsRangeConstraints++; if (whereIsBv == 0) { // bv < E bounds.Add(new ComprehensionExpr.IntBoundedPool(null, e1)); @@ -445,6 +456,7 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte } break; case BinaryExpr.ResolvedOpcode.Le: + conjunctsQualifyingAsRangeConstraints++; if (e0.Type.IsNumericBased(Type.NumericPersuasion.Int)) { if (whereIsBv == 0) { // bv <= E @@ -469,6 +481,7 @@ protected override bool VisitOneExpression(Expression expr, BoundsDiscoveryConte break; } } + constraintConsistsSolelyOfRangeConstraints = conjunctsQualifyingAsRangeConstraints == totalNumberOfConjuncts; return bounds; } diff --git a/Source/DafnyCore/Resolver/ModuleResolver.cs b/Source/DafnyCore/Resolver/ModuleResolver.cs index b8a980b412..b697df8ac0 100644 --- a/Source/DafnyCore/Resolver/ModuleResolver.cs +++ b/Source/DafnyCore/Resolver/ModuleResolver.cs @@ -1166,6 +1166,25 @@ public void ResolveTopLevelDecls_Core(List declarations, // Compute ghost interests, figure out native types, check agreement among datatype destructors, and determine tail calls. if (reporter.Count(ErrorLevel.Error) == prevErrorCount) { foreach (TopLevelDecl d in declarations) { + void CheckIfCompilable(RedirectingTypeDecl declWithConstraint) { + var constraintIsCompilable = true; + + // Check base type + var baseType = (declWithConstraint.Var?.Type ?? ((NewtypeDecl)declWithConstraint).BaseType).NormalizeExpandKeepConstraints(); + if (baseType.AsRedirectingType is (SubsetTypeDecl or NewtypeDecl) and var baseDecl) { + CheckIfCompilable(baseDecl); + constraintIsCompilable &= baseDecl.ConstraintIsCompilable; + } + + // Check the type's constraint + if (declWithConstraint.Constraint != null) { + constraintIsCompilable &= ExpressionTester.CheckIsCompilable(Options, null, declWithConstraint.Constraint, + new CodeContextWrapper(declWithConstraint, true)); + } + + declWithConstraint.ConstraintIsCompilable = constraintIsCompilable; + } + if (d is IteratorDecl) { var iter = (IteratorDecl)d; iter.SubExpressions.ForEach(e => CheckExpression(e, this, iter)); @@ -1177,9 +1196,7 @@ public void ResolveTopLevelDecls_Core(List declarations, } else if (d is SubsetTypeDecl subsetTypeDecl) { Contract.Assert(subsetTypeDecl.Constraint != null); CheckExpression(subsetTypeDecl.Constraint, this, new CodeContextWrapper(subsetTypeDecl, true)); - subsetTypeDecl.ConstraintIsCompilable = - ExpressionTester.CheckIsCompilable(Options, null, subsetTypeDecl.Constraint, new CodeContextWrapper(subsetTypeDecl, true)); - subsetTypeDecl.CheckedIfConstraintIsCompilable = true; + CheckIfCompilable(subsetTypeDecl); if (subsetTypeDecl.Witness != null) { CheckExpression(subsetTypeDecl.Witness, this, new CodeContextWrapper(subsetTypeDecl, subsetTypeDecl.WitnessKind == SubsetTypeDecl.WKind.Ghost)); @@ -1193,13 +1210,15 @@ public void ResolveTopLevelDecls_Core(List declarations, if (newtypeDecl.Var != null) { Contract.Assert(newtypeDecl.Constraint != null); CheckExpression(newtypeDecl.Constraint, this, new CodeContextWrapper(newtypeDecl, true)); - if (newtypeDecl.Witness != null) { - CheckExpression(newtypeDecl.Witness, this, new CodeContextWrapper(newtypeDecl, newtypeDecl.WitnessKind == SubsetTypeDecl.WKind.Ghost)); - } } - if (newtypeDecl.Witness != null && newtypeDecl.WitnessKind == SubsetTypeDecl.WKind.Compiled) { - var codeContext = new CodeContextWrapper(newtypeDecl, newtypeDecl.WitnessKind == SubsetTypeDecl.WKind.Ghost); - ExpressionTester.CheckIsCompilable(Options, this, newtypeDecl.Witness, codeContext); + CheckIfCompilable(newtypeDecl); + + if (newtypeDecl.Witness != null) { + CheckExpression(newtypeDecl.Witness, this, new CodeContextWrapper(newtypeDecl, newtypeDecl.WitnessKind == SubsetTypeDecl.WKind.Ghost)); + if (newtypeDecl.WitnessKind == SubsetTypeDecl.WKind.Compiled) { + var codeContext = new CodeContextWrapper(newtypeDecl, newtypeDecl.WitnessKind == SubsetTypeDecl.WKind.Ghost); + ExpressionTester.CheckIsCompilable(Options, this, newtypeDecl.Witness, codeContext); + } } FigureOutNativeType(newtypeDecl); @@ -1628,8 +1647,11 @@ public void ResolveTopLevelDecls_Core(List declarations, } } } - // Verifies that, in all compiled places, subset types in comprehensions have a compilable constraint - new SubsetConstraintGhostChecker(this.Reporter).Traverse(declarations); + + if (reporter.Count(ErrorLevel.Error) == prevErrorCount) { + // Verifies that, in all compiled places, subset types in comprehensions have a compilable constraint + new SubsetConstraintGhostChecker(this.Reporter).Traverse(declarations); + } } /// @@ -2107,11 +2129,13 @@ private void FigureOutNativeType(NewtypeDecl dd) { ddConstraint = ddWhereConstraintsAre.Constraint; } List bounds; + bool constraintConsistsSolelyOfRangeConstraints; if (ddVar == null) { // There are no bounds at all bounds = new List(); + constraintConsistsSolelyOfRangeConstraints = true; } else { - bounds = DiscoverAllBounds_SingleVar(ddVar, ddConstraint); + bounds = DiscoverAllBounds_SingleVar(ddVar, ddConstraint, out constraintConsistsSolelyOfRangeConstraints); } // Find which among the allowable native types can hold "dd". Give an @@ -2161,6 +2185,13 @@ void UpdateBounds(BigInteger? lo, BigInteger? hi) { foreach (var nativeT in bigEnoughNativeTypes) { if (Options.Backend.SupportedNativeTypes.Contains(nativeT.Name)) { dd.NativeType = nativeT; + if (constraintConsistsSolelyOfRangeConstraints) { + dd.NativeTypeRangeImpliesAllConstraints = true; + } + if (constraintConsistsSolelyOfRangeConstraints && nativeT.Sel != NativeType.Selection.Number && + lowBound == nativeT.LowerBound && highBound == nativeT.UpperBound) { + dd.TargetTypeCoversAllBitPatterns = true; + } break; } } @@ -2169,8 +2200,13 @@ void UpdateBounds(BigInteger? lo, BigInteger? hi) { // one particular native type, in which case that must have been the one picked. if (nativeTypeChoices != null && nativeTypeChoices.Count == 1) { Contract.Assert(dd.NativeType == nativeTypeChoices[0]); + if (dd.TargetTypeCoversAllBitPatterns) { + reporter.Info(MessageSource.Resolver, dd.tok, + $"newtype {dd.Name} is target-complete for {{:nativeType \"{dd.NativeType.Name}\"}}"); + } } else { var detectedRange = emptyRange ? "empty" : $"{lowBound} .. {highBound}"; + var targetComplete = dd.TargetTypeCoversAllBitPatterns ? "target-complete " : ""; reporter.Info(MessageSource.Resolver, dd.tok, $"newtype {dd.Name} resolves as {{:nativeType \"{dd.NativeType.Name}\"}} (detected range: {detectedRange})"); } diff --git a/Source/DafnyCore/Resolver/SubsetConstraintGhostChecker.cs b/Source/DafnyCore/Resolver/SubsetConstraintGhostChecker.cs index 5c4fbae71e..f7b3db3939 100644 --- a/Source/DafnyCore/Resolver/SubsetConstraintGhostChecker.cs +++ b/Source/DafnyCore/Resolver/SubsetConstraintGhostChecker.cs @@ -81,28 +81,16 @@ public override bool Traverse(Expression expr, [CanBeNull] string field, [CanBeN return base.Traverse(expr, field, parent); } - string what = e.WhatKind; - if (e is QuantifierExpr or SetComprehension or MapComprehension) { foreach (var boundVar in e.BoundVars) { - if (boundVar.Type.AsSubsetType is { - Constraint: var constraint, - ConstraintIsCompilable: false and var constraintIsCompilable - } and var subsetTypeDecl - ) { - if (!subsetTypeDecl.CheckedIfConstraintIsCompilable) { - // Builtin types were never resolved. - constraintIsCompilable = - ExpressionTester.CheckIsCompilable(reporter.Options, null, constraint, new CodeContextWrapper(subsetTypeDecl, true)); - subsetTypeDecl.CheckedIfConstraintIsCompilable = true; - subsetTypeDecl.ConstraintIsCompilable = constraintIsCompilable; - } + if (boundVar.Type.NormalizeExpandKeepConstraints().AsRedirectingType is (SubsetTypeDecl or NewtypeDecl) and var declWithConstraint) { + if (!declWithConstraint.ConstraintIsCompilable) { - if (!constraintIsCompilable) { IToken finalToken = boundVar.tok; - if (constraint.tok.line != 0) { + if (declWithConstraint.Constraint != null && declWithConstraint.Constraint.tok.line != 0) { var errorCollector = new FirstErrorCollector(reporter.Options); - ExpressionTester.CheckIsCompilable(null, errorCollector, constraint, new CodeContextWrapper(subsetTypeDecl, true)); + ExpressionTester.CheckIsCompilable(null, errorCollector, declWithConstraint.Constraint, + new CodeContextWrapper(declWithConstraint, true)); if (errorCollector.Collected) { finalToken = new NestedToken(finalToken, errorCollector.FirstCollectedToken, "The constraint is not compilable because " + errorCollector.FirstCollectedMessage @@ -110,7 +98,8 @@ public override bool Traverse(Expression expr, [CanBeNull] string field, [CanBeN } } this.reporter.Error(MessageSource.Resolver, finalToken, - $"{boundVar.Type} is a subset type and its constraint is not compilable, hence it cannot yet be used as the type of a bound variable in {what}."); + $"{boundVar.Type} is a {declWithConstraint.WhatKind} and its constraint is not compilable, " + + $"hence it cannot yet be used as the type of a bound variable in {e.WhatKind}."); } } } diff --git a/Source/DafnyCore/Rewriters/ExpectContracts.cs b/Source/DafnyCore/Rewriters/ExpectContracts.cs index 79feda0ddb..738996a252 100644 --- a/Source/DafnyCore/Rewriters/ExpectContracts.cs +++ b/Source/DafnyCore/Rewriters/ExpectContracts.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; -using Microsoft.Extensions.Logging.Abstractions; using static Microsoft.Dafny.RewriterErrors; namespace Microsoft.Dafny; @@ -222,39 +221,50 @@ private static void RegisterResolvedByMethod(Function f, TopLevelDeclWithMembers } /// - /// Add wrappers for certain top-level declarations in the given module. - /// This runs after the first pass of resolution so that it has access to - /// ghostness information, attributes and call targets. + /// Adds wrappers for certain top-level declarations in the given + /// program and redirects callers to call those wrappers instead of + /// the original members. + /// + /// This runs after resolution so that it has access to ghostness + /// information, attributes and call targets and after verification + /// because that makes the interaction with the refinement transformer + /// more straightforward. /// - /// The module to generate wrappers for and in. - internal override void PostResolveIntermediate(ModuleDefinition moduleDefinition) { - // Keep a list of members to wrap so that we don't modify the collection we're iterating over. - List<(TopLevelDeclWithMembers, MemberDecl)> membersToWrap = new(); - - moduleDefinition.CallRedirector = new(Reporter); - - // Find module members to wrap. - foreach (var topLevelDecl in moduleDefinition.TopLevelDecls.OfType()) { - foreach (var decl in topLevelDecl.Members) { - if (ShouldGenerateWrapper(decl)) { - membersToWrap.Add((topLevelDecl, decl)); + /// The program to generate wrappers for and in. + public override void PostVerification(Program program) { + // Create wrappers + foreach (var moduleDefinition in program.Modules()) { + + // Keep a list of members to wrap so that we don't modify the collection we're iterating over. + List<(TopLevelDeclWithMembers, MemberDecl)> membersToWrap = new(); + + moduleDefinition.CallRedirector = new(Reporter); + + // Find module members to wrap. + foreach (var topLevelDecl in moduleDefinition.TopLevelDecls.OfType()) { + foreach (var decl in topLevelDecl.Members) { + if (ShouldGenerateWrapper(decl)) { + membersToWrap.Add((topLevelDecl, decl)); + } } } - } - // Generate a wrapper for each of the members identified above. - foreach (var (topLevelDecl, decl) in membersToWrap) { - GenerateWrapper(moduleDefinition, topLevelDecl, decl); - } - moduleDefinition.CallRedirector.NewRedirections = wrappedDeclarations; - } + // Generate a wrapper for each of the members identified above. This + // need to happen after all declarations to wrap have been identified + // because it adds new declarations and would invalidate the iterator + // used during identification. + foreach (var (topLevelDecl, decl) in membersToWrap) { + GenerateWrapper(moduleDefinition, topLevelDecl, decl); + } + moduleDefinition.CallRedirector.NewRedirections = wrappedDeclarations; - public override void PostVerification(Program program) { - foreach (var module in program.CompileModules) { - foreach (var topLevelDecl in module.TopLevelDecls.OfType()) { + // Put redirections in place. Any wrappers to call will be in either + // this module or to a previously-processed module, so they'll already + // exist. + foreach (var topLevelDecl in moduleDefinition.TopLevelDecls.OfType()) { foreach (var decl in topLevelDecl.Members) { if (decl is ICallable callable) { - module.CallRedirector?.Visit(callable, decl); + moduleDefinition.CallRedirector?.Visit(callable, decl); } } } @@ -264,7 +274,7 @@ public override void PostVerification(Program program) { return; } - foreach (var module in program.CompileModules) { + foreach (var module in program.Modules()) { if (module.CallRedirector == null) { continue; } diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs b/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs index cca3b5ce9b..7d376e6195 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs @@ -755,7 +755,7 @@ IEnumerable GuessWitnesses(BoundVar x, Expression expr) { yield return lit; } - var bounds = ModuleResolver.DiscoverAllBounds_SingleVar(x, expr); + var bounds = ModuleResolver.DiscoverAllBounds_SingleVar(x, expr, out _); foreach (var bound in bounds) { if (bound is ComprehensionExpr.IntBoundedPool) { var bnd = (ComprehensionExpr.IntBoundedPool)bound; diff --git a/Source/DafnyDriver.Test/FormatterCommandTest.cs b/Source/DafnyDriver.Test/FormatterCommandTest.cs new file mode 100644 index 0000000000..cd3ae0260a --- /dev/null +++ b/Source/DafnyDriver.Test/FormatterCommandTest.cs @@ -0,0 +1,65 @@ +using System.Diagnostics; +using System.IO.Pipelines; +using System.Reactive.Concurrency; +using System.Reflection; +using System.Text; +using Microsoft.Dafny; +using Microsoft.Extensions.Logging.Abstractions; +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.JsonRpc.Client; +using OmniSharp.Extensions.JsonRpc.Serialization; +using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace DafnyDriver.Test; + +public class FormatterCommandTest { + [Fact] + public async Task GitIssueVSCode452FormattingWorksOnBigFiles() { + // We write to a temporary file + var oneSource = (int i) => $@"function Test{i}(x: int): int +// This returns {i} if x is greater than 20 +{{ + if x < 10 then + 0 + else if x < 20 then + 1 + else {i} +}} + +"; + + var oneSourceFormatted = (int i) => $@"function Test{i}(x: int): int + // This returns {i} if x is greater than 20 +{{ + if x < 10 then + 0 + else if x < 20 then + 1 + else {i} +}} + +"; + var source = ""; + var sourceFormatted = ""; + for (var i = 0; i < 1000; i++) { + source += oneSource(i); + sourceFormatted += oneSourceFormatted(i); + } + + var file = Path.GetTempFileName() + ".dfy"; + + await File.WriteAllTextAsync(file, source); + StringWriter strWriter = new StringWriter(); + var options = DafnyOptions.Create(strWriter, null, + file + ); + + var exitValue = await FormatCommand.DoFormatting(options); + Assert.Equal(0, (int)exitValue); + + var result = await File.ReadAllTextAsync(file); + Assert.Equal(sourceFormatted, result); + File.Delete(file); + } +} diff --git a/Source/DafnyDriver/Commands/FormatCommand.cs b/Source/DafnyDriver/Commands/FormatCommand.cs index 130f69a702..62ba02ef9e 100644 --- a/Source/DafnyDriver/Commands/FormatCommand.cs +++ b/Source/DafnyDriver/Commands/FormatCommand.cs @@ -9,7 +9,7 @@ namespace Microsoft.Dafny; -static class FormatCommand { +public static class FormatCommand { public static IEnumerable