diff --git a/Source/DafnyCore/AST/Expressions/Conditional/Patterns/ExtendedPattern.cs b/Source/DafnyCore/AST/Expressions/Conditional/Patterns/ExtendedPattern.cs index 43a94fe1b36..e745cb7fc71 100644 --- a/Source/DafnyCore/AST/Expressions/Conditional/Patterns/ExtendedPattern.cs +++ b/Source/DafnyCore/AST/Expressions/Conditional/Patterns/ExtendedPattern.cs @@ -93,6 +93,8 @@ public abstract class ExtendedPattern : TokenNode { return; } + Contract.Assert(tupleTypeDecl.Ctors.Count == 1); + Contract.Assert(tupleTypeDecl.Ctors[0] == tupleTypeDecl.GroundingCtor); idpat.Ctor = tupleTypeDecl.GroundingCtor; //We expect the number of arguments in the type of the matchee and the provided pattern to match, except if the pattern is a bound variable diff --git a/Source/DafnyCore/AST/TypeDeclarations/IndDatatypeDecl.cs b/Source/DafnyCore/AST/TypeDeclarations/IndDatatypeDecl.cs index dd9749718a7..6af1191fe99 100644 --- a/Source/DafnyCore/AST/TypeDeclarations/IndDatatypeDecl.cs +++ b/Source/DafnyCore/AST/TypeDeclarations/IndDatatypeDecl.cs @@ -1,17 +1,33 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Linq; namespace Microsoft.Dafny; public class IndDatatypeDecl : DatatypeDecl { public override string WhatKind { get { return "datatype"; } } - public DatatypeCtor GroundingCtor; // set during resolution + [FilledInDuringResolution] public DatatypeCtor GroundingCtor; // set during resolution (possibly to null) public override DatatypeCtor GetGroundingCtor() { - return GroundingCtor; + return GroundingCtor ?? Ctors.FirstOrDefault(ctor => ctor.IsGhost, Ctors[0]); } - public bool[] TypeParametersUsedInConstructionByGroundingCtor; // set during resolution; has same length as the number of type arguments + private bool[] typeParametersUsedInConstructionByGroundingCtor; + + public bool[] TypeParametersUsedInConstructionByGroundingCtor { + get { + if (typeParametersUsedInConstructionByGroundingCtor == null) { + typeParametersUsedInConstructionByGroundingCtor = new bool[TypeArgs.Count]; + for (var i = 0; i < typeParametersUsedInConstructionByGroundingCtor.Length; i++) { + typeParametersUsedInConstructionByGroundingCtor[i] = true; + } + } + return typeParametersUsedInConstructionByGroundingCtor; + } + set { + typeParametersUsedInConstructionByGroundingCtor = value; + } + } public enum ES { NotYetComputed, Never, ConsultTypeArguments } public ES EqualitySupport = ES.NotYetComputed; diff --git a/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs b/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs index ce41305f19b..8ff7a2f43de 100644 --- a/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs +++ b/Source/DafnyCore/AST/TypeDeclarations/NewtypeDecl.cs @@ -49,6 +49,39 @@ public class NewtypeDecl : TopLevelDeclWithMembers, RevealableTypeDecl, Redirect this.NewSelfSynonym(); } + /// + /// Return .BaseType instantiated with "typeArgs", but only look at the part of .BaseType that is in scope. + /// + public Type RhsWithArgument(List typeArgs) { + Contract.Requires(typeArgs != null); + Contract.Requires(typeArgs.Count == TypeArgs.Count); + var scope = Type.GetScope(); + var rtd = BaseType.AsRevealableType; + if (rtd != null) { + Contract.Assume(rtd.AsTopLevelDecl.IsVisibleInScope(scope)); + if (!rtd.IsRevealedInScope(scope)) { + // type is actually hidden in this scope + return rtd.SelfSynonym(typeArgs); + } + } + return RhsWithArgumentIgnoringScope(typeArgs); + } + /// + /// Returns the declared .BaseType but with formal type arguments replaced by the given actuals. + /// + public Type RhsWithArgumentIgnoringScope(List typeArgs) { + Contract.Requires(typeArgs != null); + Contract.Requires(typeArgs.Count == TypeArgs.Count); + // Instantiate with the actual type arguments + if (typeArgs.Count == 0) { + // this optimization seems worthwhile + return BaseType; + } else { + var subst = TypeParameter.SubstitutionMap(TypeArgs, typeArgs); + return BaseType.Subst(subst); + } + } + public TopLevelDecl AsTopLevelDecl => this; public TypeDeclSynonymInfo SynonymInfo { get; set; } diff --git a/Source/DafnyCore/AST/TypeDeclarations/TupleTypeDecl.cs b/Source/DafnyCore/AST/TypeDeclarations/TupleTypeDecl.cs index 0e6b8673243..590e4915204 100644 --- a/Source/DafnyCore/AST/TypeDeclarations/TupleTypeDecl.cs +++ b/Source/DafnyCore/AST/TypeDeclarations/TupleTypeDecl.cs @@ -39,6 +39,7 @@ private TupleTypeDecl(ModuleDefinition systemModule, List typeArg new List(), new List(), attributes, false) { Contract.Requires(systemModule != null); Contract.Requires(typeArgs != null); + Contract.Assert(Ctors.Count == 1); ArgumentGhostness = argumentGhostness; foreach (var ctor in Ctors) { ctor.EnclosingDatatype = this; // resolve here @@ -63,6 +64,10 @@ private TupleTypeDecl(ModuleDefinition systemModule, List typeArg } return ts; } + + /// + /// Creates the one and only constructor of the tuple type. + /// private static List CreateConstructors(List typeArgs, List argumentGhostness) { Contract.Requires(typeArgs != null); var formals = new List(); diff --git a/Source/DafnyCore/Resolver/ModuleResolver.cs b/Source/DafnyCore/Resolver/ModuleResolver.cs index c9188e490fe..b4a60bff93f 100644 --- a/Source/DafnyCore/Resolver/ModuleResolver.cs +++ b/Source/DafnyCore/Resolver/ModuleResolver.cs @@ -1272,6 +1272,26 @@ public partial class ModuleResolver { FillInPostConditionsAndBodiesOfPrefixLemmas(declarations); } + // An inductive datatype is allowed to be defined as an empty type. For example, in + // predicate P(x: int) { false } + // type Subset = x: int | P(x) witness * + // datatype Record = Record(Subset) + // Record is an empty type, because Subset is, since P(x) is always false. But if P(x) + // was instead defined to be true for some x's, then Record would be nonempty. Determining whether or + // not Record is empty goes well beyond the syntactic checks of the type system. + // + // However, if a datatype is empty because of some "obvious" cycle among datatype definitions, then + // that is both detectable by syntactic checks and likely unintended by the programmer. Therefore, + // we search for such type declarations and give error messages if something is found. + if (reporter.Count(ErrorLevel.Error) == prevErrorCount) { + foreach (var dtd in declarations.ConvertAll(decl => decl as IndDatatypeDecl).Where(dtd => dtd != null && dtd.Ctors.Count != 0)) { + if (AreThereAnyObviousSignsOfEmptiness(UserDefinedType.FromTopLevelDecl(dtd.tok, dtd), new HashSet())) { + reporter.Warning(MessageSource.Resolver, ResolutionErrors.ErrorId.r_empty_cyclic_datatype, dtd.tok, + $"because of cyclic dependencies among constructor argument types, no instances of datatype '{dtd.Name}' can be constructed"); + } + } + } + // Perform the stratosphere check on inductive datatypes, and compute to what extent the inductive datatypes require equality support if (reporter.Count(ErrorLevel.Error) == prevErrorCount) { // because SccStratosphereCheck depends on subset-type/newtype base types being successfully resolved foreach (var dtd in datatypeDependencies.TopologicallySortedComponents()) { @@ -2563,6 +2583,35 @@ public ReportOtherAdditionalInformation_Visitor(ModuleResolver resolver) } } + private bool AreThereAnyObviousSignsOfEmptiness(Type type, ISet beingVisited) { + type = type.NormalizeExpandKeepConstraints(); // cut through type proxies, type synonyms, but being mindful of what's in scope + if (type is UserDefinedType { ResolvedClass: var cl } udt) { + Contract.Assert(cl != null); + if (ArrowType.IsTotalArrowTypeName(cl.Name)) { + return AreThereAnyObviousSignsOfEmptiness(udt.TypeArgs.Last(), beingVisited); + } else if (cl is SubsetTypeDecl subsetTypeDecl) { + return AreThereAnyObviousSignsOfEmptiness(subsetTypeDecl.RhsWithArgument(udt.TypeArgs), beingVisited); + } else if (cl is NewtypeDecl newtypeDecl) { + return AreThereAnyObviousSignsOfEmptiness(newtypeDecl.RhsWithArgument(udt.TypeArgs), beingVisited); + } + if (cl is IndDatatypeDecl datatypeDecl) { + if (beingVisited.Contains(datatypeDecl)) { + // This datatype may be empty, but it's definitely empty if we consider only the constructors that have been visited + // since AreThereAnyObviousSignsOfEmptiness was called from IsObviouslyEmpty. + return true; + } + beingVisited.Add(datatypeDecl); + var typeMap = TypeParameter.SubstitutionMap(datatypeDecl.TypeArgs, udt.TypeArgs); + var isEmpty = datatypeDecl.Ctors.TrueForAll(ctor => + ctor.Formals.Exists(formal => AreThereAnyObviousSignsOfEmptiness(formal.Type.Subst(typeMap), beingVisited))); + beingVisited.Remove(datatypeDecl); + return isEmpty; + } + } + + return false; + } + /// /// Check that the SCC of 'startingPoint' can be carved up into stratospheres in such a way that each /// datatype has some value that can be constructed from datatypes in lower stratospheres only. @@ -2592,7 +2641,7 @@ public ReportOtherAdditionalInformation_Visitor(ModuleResolver resolver) if (dt.GroundingCtor != null) { // previously cleared } else if (ComputeGroundingCtor(dt)) { - Contract.Assert(dt.GroundingCtor != null); // should have been set by the successful call to StratosphereCheck) + Contract.Assert(dt.GroundingCtor != null); // should have been set by the successful call to ComputeGroundingCtor) clearedThisRound++; totalCleared++; } @@ -2603,19 +2652,13 @@ public ReportOtherAdditionalInformation_Visitor(ModuleResolver resolver) } else if (clearedThisRound != 0) { // some progress was made, so let's keep going } else { - // whatever is in scc-cleared now failed to pass the test - foreach (var dt in scc) { - if (dt.GroundingCtor == null) { - reporter.Error(MessageSource.Resolver, dt, "because of cyclic dependencies among constructor argument types, no instances of datatype '{0}' can be constructed", dt.Name); - } - } return; } } } /// - /// Check that the datatype has some constructor all whose argument types can be constructed. + /// Check if the datatype has some constructor all whose argument types can be constructed. /// Returns 'true' and sets dt.GroundingCtor if that is the case. /// bool ComputeGroundingCtor(IndDatatypeDecl dt) { @@ -2623,7 +2666,7 @@ public ReportOtherAdditionalInformation_Visitor(ModuleResolver resolver) Contract.Requires(dt.GroundingCtor == null); // the intention is that this method be called only when GroundingCtor hasn't already been set Contract.Ensures(!Contract.Result() || dt.GroundingCtor != null); - // Stated differently, check that there is some constuctor where no argument type goes to the same stratum. + // Stated differently, check that there is some constructor where no argument type goes to the same stratum. DatatypeCtor groundingCtor = null; ISet lastTypeParametersUsed = null; foreach (DatatypeCtor ctor in dt.Ctors) { diff --git a/Source/DafnyCore/Resolver/ResolutionErrors.cs b/Source/DafnyCore/Resolver/ResolutionErrors.cs index 5c0eac59c62..69a6fa57649 100644 --- a/Source/DafnyCore/Resolver/ResolutionErrors.cs +++ b/Source/DafnyCore/Resolver/ResolutionErrors.cs @@ -82,7 +82,8 @@ public enum ErrorId { r_assert_only_before_after, r_failure_methods_deprecated, r_member_only_assumes_other, - r_member_only_has_no_before_after + r_member_only_has_no_before_after, + r_empty_cyclic_datatype } static ResolutionErrors() { diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionTranslator.cs b/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionTranslator.cs index 5494a3a705c..810e22102dd 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionTranslator.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionTranslator.cs @@ -1551,7 +1551,10 @@ public ExpressionTranslator(ExpressionTranslator etran, Boogie.Expr heap) Contract.Requires(let != null); var substMap = new Dictionary(); for (int i = 0; i < let.LHSs.Count; i++) { - BoogieGenerator.AddCasePatternVarSubstitutions(let.LHSs[i], TrExpr(let.RHSs[i]), substMap); + var rhs = TrExpr(let.RHSs[i]); + var toType = let.LHSs[i].Var?.Type ?? let.LHSs[i].Expr.Type; + rhs = BoogieGenerator.CondApplyBox(rhs.tok, rhs, let.RHSs[i].Type, toType); + BoogieGenerator.AddCasePatternVarSubstitutions(let.LHSs[i], rhs, substMap); } lhss = new List(); rhss = new List(); diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionWellformed.cs b/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionWellformed.cs index d740e699064..02af1554a10 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionWellformed.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.ExpressionWellformed.cs @@ -1229,7 +1229,8 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA Contract.Assert(resultType != null); var bResult = etran.TrExpr(expr); CheckSubrange(expr.tok, bResult, expr.Type, resultType, builder); - builder.Add(TrAssumeCmdWithDependenciesAndExtend(etran, expr.tok, expr, e => Bpl.Expr.Eq(result, e), + builder.Add(TrAssumeCmdWithDependenciesAndExtend(etran, expr.tok, expr, + e => Bpl.Expr.Eq(result, CondApplyBox(expr.tok, e, expr.Type, resultType)), resultDescription)); builder.Add(TrAssumeCmd(expr.tok, etran.CanCallAssumption(expr))); builder.Add(new CommentCmd("CheckWellformedWithResult: any expression")); @@ -1369,7 +1370,6 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA void CheckWellformedLetExprWithResult(LetExpr e, WFOptions wfOptions, Bpl.Expr result, Type resultType, List locals, BoogieStmtListBuilder builder, ExpressionTranslator etran, bool checkRhs) { if (e.Exact) { - var uniqueSuffix = "#Z" + defaultIdGenerator.FreshNumericId("#Z"); var substMap = SetupBoundVarsAsLocals(e.BoundVars.ToList(), builder, locals, etran, "#Z"); Contract.Assert(e.LHSs.Count == e.RHSs.Count); // checked by resolution var varNameGen = CurrentIdGenerator.NestedFreshIdGenerator("let#"); @@ -1377,7 +1377,7 @@ private record ReadsCheckDelayer(ExpressionTranslator etran, Function selfCallsA var pat = e.LHSs[i]; var rhs = e.RHSs[i]; var nm = varNameGen.FreshId(string.Format("#{0}#", i)); - var r = new Bpl.LocalVariable(pat.tok, new Bpl.TypedIdent(pat.tok, nm, TrType(rhs.Type))); + var r = new Bpl.LocalVariable(pat.tok, new Bpl.TypedIdent(pat.tok, nm, TrType(pat.Expr.Type))); locals.Add(r); var rIe = new Bpl.IdentifierExpr(rhs.tok, r); CheckWellformedWithResult(e.RHSs[i], wfOptions, rIe, pat.Expr.Type, locals, builder, etran, "let expression binding RHS well-formed"); diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.Functions.cs b/Source/DafnyCore/Verifier/BoogieGenerator.Functions.cs index d5d67116770..2c445f9c5c8 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.Functions.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.Functions.cs @@ -876,7 +876,7 @@ public partial class BoogieGenerator { } var etranBody = layer == null ? etran : etran.LimitedFunctions(f, ly); - var trbody = etranBody.TrExpr(bodyWithSubst); + var trbody = CondApplyBox(f.tok, etranBody.TrExpr(bodyWithSubst), f.Body.Type, f.ResultType); tastyVegetarianOption = BplAnd(etranBody.CanCallAssumption(bodyWithSubst), BplAnd(TrFunctionSideEffect(bodyWithSubst, etranBody), Bpl.Expr.Eq(funcAppl, trbody))); } diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs b/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs index c8d09035bcb..31defc234b2 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.Types.cs @@ -1156,6 +1156,8 @@ void AddRedirectingTypeDeclAxioms(bool is_alloc, T dd, string fullName) } else if (fromType.IsTraitType) { // cast from a non-reference trait return UnboxIfBoxed(r, toType); + } else if (fromType.Equals(toType)) { + return r; } else { Contract.Assert(false, $"No translation implemented from {fromType} to {toType}"); } diff --git a/Source/DafnyCore/Verifier/BoogieGenerator.cs b/Source/DafnyCore/Verifier/BoogieGenerator.cs index 5c75bffc2f5..18c4f0c2efa 100644 --- a/Source/DafnyCore/Verifier/BoogieGenerator.cs +++ b/Source/DafnyCore/Verifier/BoogieGenerator.cs @@ -3271,17 +3271,12 @@ class BoilerplateTriple { // a triple that is now a quintuple if (!ModeledAsBoxType(fromType) && (toType == null || ModeledAsBoxType(toType))) { // if "e" denotes "Unbox(E): T", then just return "E" - var coerce = e as Bpl.NAryExpr; - if (coerce != null && coerce.Fun is Bpl.TypeCoercion) { + if (e is Bpl.NAryExpr { Fun: Bpl.TypeCoercion } coerce) { Contract.Assert(coerce.Args.Count == 1); - Contract.Assert(Bpl.Type.Equals(((Bpl.TypeCoercion)coerce.Fun).Type, TrType(fromType))); ; - var call = coerce.Args[0] as Bpl.NAryExpr; - if (call != null && call.Fun is Bpl.FunctionCall) { - var fn = (Bpl.FunctionCall)call.Fun; - if (fn.FunctionName == "$Unbox") { - Contract.Assert(call.Args.Count == 1); - return call.Args[0]; - } + Contract.Assert(Bpl.Type.Equals(((Bpl.TypeCoercion)coerce.Fun).Type, TrType(fromType))); + if (coerce.Args[0] is Bpl.NAryExpr { Fun: Bpl.FunctionCall { FunctionName: "$Unbox" } } call) { + Contract.Assert(call.Args.Count == 1); + return call.Args[0]; } } // return "Box(e)" diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy index 3615f08e749..fff9f324eae 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy @@ -10,10 +10,10 @@ module TestInductiveDatatypes datatype Record = Ctor(T) datatype RecInt = Ctor(Record) // this is fine - datatype Rec_Forever = Ctor(Record) // error + datatype Rec_Forever = Ctor(Record) // warning: no instances datatype Rec_Stops = Cons(Record, Rec_Stops) | Nil // this is okay - datatype D = Ctor(E>) // error: illegal cycle + datatype D = Ctor(E>) // warning: no instances datatype E = Ctor(T) // the following is okay @@ -35,7 +35,7 @@ module MoreInductive { datatype M = All(List) datatype H<'a> = HH('a, Tree<'a>) - datatype K<'a> = KK('a, Tree>) // error + datatype K<'a> = KK('a, Tree>) // warning: no instances datatype L<'a> = LL('a, Tree>>) } @@ -61,7 +61,7 @@ module TestCoinductiveDatatypes datatype FiniteEnough_Class = Ctor(MyClass) datatype FiniteEnough_Co = Ctor(LazyRecord) datatype FiniteEnough_Dt = Ctor(GenericDt) // fine - datatype NotFiniteEnough_Dt = Ctor(GenericRecord) // error + datatype NotFiniteEnough_Dt = Ctor(GenericRecord) // warning: no instances } diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy.expect index 9f58bc94721..5a5f281ae11 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy.expect +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/Coinductive.dfy.expect @@ -1,7 +1,7 @@ -Coinductive.dfy(13,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'Rec_Forever' can be constructed -Coinductive.dfy(16,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'D' can be constructed -Coinductive.dfy(38,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'K' can be constructed -Coinductive.dfy(64,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'NotFiniteEnough_Dt' can be constructed +Coinductive.dfy(13,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'Rec_Forever' can be constructed +Coinductive.dfy(16,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'D' can be constructed +Coinductive.dfy(38,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'K' can be constructed +Coinductive.dfy(64,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'NotFiniteEnough_Dt' can be constructed Coinductive.dfy(93,8): Error: a greatest predicate can be called recursively only in positive positions Coinductive.dfy(94,8): Error: a greatest predicate can be called recursively only in positive positions Coinductive.dfy(95,8): Error: a greatest predicate can be called recursively only in positive positions @@ -38,4 +38,4 @@ Coinductive.dfy(355,19): Error: a greatest predicate can be called recursively o Coinductive.dfy(355,44): Error: a greatest predicate can be called recursively only in positive positions and cannot sit inside an unbounded existential quantifier Coinductive.dfy(358,19): Error: a greatest predicate can be called recursively only in positive positions and cannot sit inside an unbounded existential quantifier Coinductive.dfy(358,46): Error: a greatest predicate can be called recursively only in positive positions and cannot sit inside an unbounded existential quantifier -40 resolution/type errors detected in Coinductive.dfy +36 resolution/type errors detected in Coinductive.dfy diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy index dabc4a9fdf2..fe0aefea006 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy @@ -1040,10 +1040,10 @@ module CycleError2 { module CycleErrors3 { type A = (B, D) type B = C - class C { + class C { // error: since A is not auto-init, class C needs a constructor var a: A // this is fine } - datatype D = Make(A, B, C) // error: cannot construct a D + datatype D = Make(A, B, C) // warning: D is empty } module CycleError4 { type A = B // error: cycle: A -> B -> A diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy.expect index 9782a2aecd0..e6086abf38e 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy.expect +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/ResolutionErrors.dfy.expect @@ -133,7 +133,8 @@ ResolutionErrors.dfy(1023,22): Error: Type or type parameter is not declared in ResolutionErrors.dfy(1030,7): Error: type-synonym cycle: A -> A ResolutionErrors.dfy(1033,7): Error: cycle among redirecting types (newtypes, subset types, type synonyms): A -> B -> A ResolutionErrors.dfy(1037,7): Error: cycle among redirecting types (newtypes, subset types, type synonyms): A -> B -> A -ResolutionErrors.dfy(1046,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'D' can be constructed +ResolutionErrors.dfy(1046,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'D' can be constructed +ResolutionErrors.dfy(1043,8): Error: class 'C' with fields without known initializers, like 'a' of type 'A', must declare a constructor ResolutionErrors.dfy(1049,7): Error: cycle among redirecting types (newtypes, subset types, type synonyms): A -> B -> A ResolutionErrors.dfy(1054,7): Error: cycle among redirecting types (newtypes, subset types, type synonyms): A -> B -> A ResolutionErrors.dfy(1059,7): Error: type-synonym cycle: A -> A diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy index 1950c3ea51b..9b61908c456 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy @@ -20,7 +20,7 @@ class C { datatype D = A -datatype NeverendingList = Cons(int, NeverendingList) // error: no grounding constructor + datatype MutuallyRecursiveDataType = FromANumber(int) | // this is the base case @@ -226,11 +226,11 @@ module ArrayTests { // --------------------- module OtherCycles0 { - datatype A = Ctor(A -> A) // error: cannot be constructed - datatype B = Ctor(int -> B) // error: cannot be constructed + datatype A = Ctor(A -> A) // warning: empty datatype + datatype B = Ctor(int -> B) // warning: empty datatype - datatype Cycle = Cycle(Cyc) // error: cannot be constructed - type Cyc = c: Cycle | true + datatype Cycle = Cycle(Cyc) // warning: empty datatype + type Cyc = c: Cycle | true // error: dependency: Cyc -> Cycle -> Cyc } module OtherCycles1 { @@ -246,7 +246,36 @@ module OtherCycles1 { } module OtherCycles2 { - datatype CycleW = CycleW(CycW) + // the next line uses a general arrow + datatype CycleW = CycleW(int ~> CycW) type CycW = c: CycleW | true witness W() // error: dependency cycle W -> CycW -> CycleW function W(): CycleW } + +module OtherCycles3 { + // the next line uses a partial arrow + datatype CycleW = CycleW(int -> CycW) // warning: no instance possible + type CycW = c: CycleW | true witness W() // error: dependency cycle W -> CycW -> CycleW + function W(): CycleW +} + +module OtherCycles4 { + // the next line uses a total arrow + datatype CycleW = CycleW(int -> CycW) // warning: because of cycle among constructor argument types, 'CycleW' is empty + type CycW = c: CycleW | true witness W() // error: dependency cycle: CycW -> W -> CycleW -> CycW + function W(): CycleW +} + +module OtherCycles5 { + // the next line uses a subset type over a total arrow + type MyTotalArrow = f: X -> Y | true + datatype CycleW = CycleW(MyTotalArrow) // warning: because of cycle among constructor argument types, 'CycleW' is empty + type CycW = c: CycleW | true witness W() // error: dependency cycle: CycW -> W -> CycleW -> CycW + function W(): CycleW +} + +module NE { + datatype NeverendingList = Cons(int, NeverendingList) // warning: empty type + datatype Growing = Make(Growing>) // warning: empty type + datatype MaybeGrowing = Make(array>) // okay, since it does not violate the cycle rule +} diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy.expect index 1aa4950a6ab..6b858119876 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy.expect +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/dafny0/TypeTests.dfy.expect @@ -29,13 +29,22 @@ TypeTests.dfy(198,10): Error: incorrect argument type for datatype constructor p TypeTests.dfy(204,10): Error: incorrect argument type for datatype constructor parameter (expected ? -> Dt, found Dt -> Dt) (contravariance for type parameter at index 0 expects ? <: Dt) TypeTests.dfy(211,10): Error: incorrect argument type for function parameter 'x' (expected ?, found set) TypeTests.dfy(222,9): Error: assignment to array element is not allowed in this context, because this is a ghost method -TypeTests.dfy(229,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'A' can be constructed -TypeTests.dfy(230,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'B' can be constructed -TypeTests.dfy(232,11): Error: because of cyclic dependencies among constructor argument types, no instances of datatype 'Cycle' can be constructed +TypeTests.dfy(229,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'A' can be constructed +TypeTests.dfy(230,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'B' can be constructed +TypeTests.dfy(232,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'Cycle' can be constructed +TypeTests.dfy(233,7): Error: recursive constraint dependency involving a subset type: Cyc -> Cycle -> Cyc TypeTests.dfy(237,20): Error: using the type being defined ('A') here would cause a logical inconsistency by defining a type whose cardinality exceeds itself (like the Continuum Transfunctioner, you might say its power would then be exceeded only by its mystery) TypeTests.dfy(238,20): Error: using the type being defined ('B') here would cause a logical inconsistency by defining a type whose cardinality exceeds itself (like the Continuum Transfunctioner, you might say its power would then be exceeded only by its mystery) TypeTests.dfy(243,20): Error: using the type being defined ('E') here would cause a logical inconsistency by defining a type whose cardinality exceeds itself (like the Continuum Transfunctioner, you might say its power would then be exceeded only by its mystery) TypeTests.dfy(244,20): Error: using the type being defined ('F') here would cause a logical inconsistency by defining a type whose cardinality exceeds itself (like the Continuum Transfunctioner, you might say its power would then be exceeded only by its mystery) TypeTests.dfy(245,20): Error: using the type being defined ('G') here would cause a logical inconsistency by defining a type whose cardinality exceeds itself (like the Continuum Transfunctioner, you might say its power would then be exceeded only by its mystery) -TypeTests.dfy(250,7): Error: recursive constraint dependency involving a subset type: CycW -> W -> CycleW -> CycW -40 resolution/type errors detected in TypeTests.dfy +TypeTests.dfy(251,7): Error: recursive constraint dependency involving a subset type: CycW -> W -> CycleW -> CycW +TypeTests.dfy(257,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'CycleW' can be constructed +TypeTests.dfy(258,7): Error: recursive constraint dependency involving a subset type: CycW -> W -> CycleW -> CycW +TypeTests.dfy(264,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'CycleW' can be constructed +TypeTests.dfy(265,7): Error: recursive constraint dependency involving a subset type: CycW -> W -> CycleW -> CycW +TypeTests.dfy(272,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'CycleW' can be constructed +TypeTests.dfy(273,7): Error: recursive constraint dependency involving a subset type: CycW -> W -> CycleW -> CycW +TypeTests.dfy(278,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'NeverendingList' can be constructed +TypeTests.dfy(279,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'Growing' can be constructed +41 resolution/type errors detected in TypeTests.dfy diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy index 48c26d1a596..3666ff043db 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy @@ -1,17 +1,24 @@ // RUN: %exits-with 2 %verify "%s" > "%t" // RUN: %diff "%s.expect" "%t" -datatype T = T(T, T) { - ghost predicate P() { - match this - case T(0, 1) => false +module A { + datatype T = T(T, T) + { + ghost predicate P() { + match this + case T(0, 1) => false // error (x2): neither constant pattern of constructor pattern has the right type + } } -} -method a() -{ - var tok := (0,0); + + method M() { + var tok := (0, 0); match tok { - case "B" => + case "B" => // error: "B" is not of type (int, int) case _ => } + } +} + +module B { + datatype T = T(T, T) // warnin: cycle prevents instances (module A has the same problem, but the warning is masked by other errors there) } diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy.expect index 420313f1329..356acecd947 100644 --- a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy.expect +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-2139.dfy.expect @@ -1,4 +1,5 @@ -git-issue-2139.dfy(14,11): Error: pattern doesn't correspond to a tuple -git-issue-2139.dfy(7,13): Error: Constant pattern used in place of datatype -git-issue-2139.dfy(7,16): Error: Constant pattern used in place of datatype +git-issue-2139.dfy(16,11): Error: pattern doesn't correspond to a tuple +git-issue-2139.dfy(9,13): Error: Constant pattern used in place of datatype +git-issue-2139.dfy(9,16): Error: Constant pattern used in place of datatype +git-issue-2139.dfy(23,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'T' can be constructed 3 resolution/type errors detected in git-issue-2139.dfy diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939a.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939a.dfy new file mode 100644 index 00000000000..ea1d9cf37ec --- /dev/null +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939a.dfy @@ -0,0 +1,220 @@ +// RUN: %exits-with 4 %verify --type-system-refresh --general-traits=datatype "%s" > "%t" +// RUN: %diff "%s.expect" "%t" + +module FromIssue0 { + trait Program { + method Compute() returns (r: Result) + } + + datatype Result = + | Bounce(next: Program) + | Done() + + datatype Trivial extends Program = + Trivial() + { + method Compute() returns (r: Result) { + return Done(); + } + } + + datatype Seq extends Program = + Seq(left: Program, right: Program) // this once gave an error, saying Seq is empty because of cyclic dependencies + { + method Compute() returns (r: Result) { + var result := left.Compute(); // error: cannot prove termination + match result + case Done() => r := right.Compute(); + case Bounce(next) => + var next' := Seq(next, right); + assert next' is Program; + return Bounce(next'); + } + } +} + +module FromIssue1 { + trait Program { + } + + datatype Trivial extends Program = + Trivial() + + datatype Seq extends Program = + Seq(left: Program, right: Program) // this once gave an error, saying Seq is empty because of cyclic dependencies +} + +module FromIssue2 { + // NoInstances has no instances + class NoInstances { + constructor () + requires false + { + } + } + + // ... and therefore, neither does D. However, this is not detected syntactically, so there are no complaints. + datatype D = D(n: NoInstances) +} + +module FromIssue3 { + trait Interface { + function getString(i: int): string + } + + datatype Wrapper extends Interface = Wrapper(inner: Interface) // this once gave the cyclic-dependency error + { + function getString(i: int): string { + inner.getString(i + 1) // error: cannot prove termination + } + } + + datatype DetectZero extends Interface = DetectZero + { + function getString(i: int): string { + if i == 0 + then "zero" + else "I don't know" + } + } + + function getWrappedDetectZero(): Interface { + Wrapper(DetectZero) + } +} + +module FromIssue4 { + trait Interface { + function GetString(i: int): string + } + + class {:extern} Store { + var interface: Interface + + constructor (interface: Interface) { + this.interface := interface; + } + + function GetInterface(): (res: Interface) + ensures res == interface + } + + datatype Wrapper extends Interface = Wrapper(inner: Store) + { + function GetString(i: int): string { + inner.GetInterface().GetString(i + 1) // error: cannot prove termination + } + } +} + +module SimpleExample0 { + trait GeneralTrait { + } + + datatype Wrapper = Wrapper(g: GeneralTrait) // this once gave the cyclic-dependency error +} + +module SimpleExample1 { + trait ReferenceTrait extends object { + } + + datatype Wrapper = Wrapper(g: ReferenceTrait) // this was always fine +} + +module Standard { + datatype List = Nil | Cons(X, List) + + datatype Op = Plus | Times + datatype Expr = Value(int) | Node(operator: Op, arguments: List) +} + +module Good0 { + datatype Mutual = M0(Nutual) | M1(Mutual) + datatype Nutual = N0(Mutual) | Easy +} + +module Good1 { + datatype Nutual = N0(Mutual) | Easy + datatype Mutual = M0(Nutual) | M1(Mutual) +} + +module Bad { + datatype T = Make(T, T) // warning: empty datatype + + datatype Mutual = M0(Nutual) | M1(Mutual) // warning: empty datatype + datatype Nutual = N0(Mutual) // warning: empty datatype +} + +// The following module contains tests for some previous boxing translation errors +module RegressionTest { + trait Interface { } + + datatype Wrapper = Wrapper(inner: Interface) + + method Test0(ww: Wrapper) { + var w: Wrapper; + w := ww as Wrapper; + } + + method Test1(i: Interface) { + var w: Wrapper; + w := Wrapper(i) as Wrapper; + } + + predicate P(x: int) { true } + + method Test() returns (ghost h: bool) { + h := forall i: int :: P(i) ==> var j: int :| true; false; + } +} + +// The following module also contains tests for some previous boxing translation errors +module OtherRegressionTests { + trait Parent extends object { } + class MyClass extends Parent { } + + datatype Brapper = Brapper(inner: Parent) + + method Testx(c: MyClass) { + var kw := + var jj: Parent := c; + Brapper(jj); + assert true; + } + + // ---- + + trait Interface { } + + datatype Wrapper extends Interface = Wrapper(inner: Interface) + + datatype DetectZero extends Interface = DetectZero + + function getWrappedDetectZero(): Interface { + var i: Interface := DetectZero; + var w: Wrapper := Wrapper(i); + w + } + + method Test(w: Wrapper) returns (i: Interface) { + i := w; + i := + var k: Wrapper := Wrapper(i); + k; + var j: Interface := + var i: Interface := DetectZero; + var w: Wrapper := Wrapper(i); + w; + } + + method SameTest() returns () { + var ui: Interface := DetectZero; + var e := Wrapper(ui); + + var kw := + var jj: Interface := DetectZero; + Wrapper(jj); + + assert true; + } +} diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939a.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939a.dfy.expect new file mode 100644 index 00000000000..bd0861ef22f --- /dev/null +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939a.dfy.expect @@ -0,0 +1,12 @@ +git-issue-4939a.dfy(142,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'T' can be constructed +git-issue-4939a.dfy(144,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'Mutual' can be constructed +git-issue-4939a.dfy(145,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'Nutual' can be constructed +git-issue-4939a.dfy(25,32): Error: cannot prove termination; try supplying a decreases clause +git-issue-4939a.dfy(68,12): Error: cannot prove termination; try supplying a decreases clause +git-issue-4939a.dfy(68,12): Error: decreases expression must be bounded below by 0 +git-issue-4939a.dfy(67,23): Related location +git-issue-4939a.dfy(105,27): Error: cannot prove termination; try supplying a decreases clause +git-issue-4939a.dfy(105,27): Error: decreases expression must be bounded below by 0 +git-issue-4939a.dfy(104,23): Related location + +Dafny program verifier finished with 12 verified, 5 errors diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939b.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939b.dfy new file mode 100644 index 00000000000..28b4d43cda7 --- /dev/null +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939b.dfy @@ -0,0 +1,31 @@ +// RUN: %exits-with 4 %verify --type-system-refresh --general-traits=datatype "%s" > "%t" +// RUN: %diff "%s.expect" "%t" + +module NoAutoInit { + trait GeneralTrait { } + trait ReferenceTrait extends object { } + + datatype A = A(g: GeneralTrait) + datatype B = B(h: ReferenceTrait) + + method Test() { + var a: A := *; + var b: B := *; + + if * { + print a, "\n"; // error: A is a possibly empty type, so "a" is subject to definite assignment + } else { + print b, "\n"; // error: B is a possibly empty type, so "b" is subject to definite assignment + } + } +} + +module ProvablyEmpty { + datatype BadList = Cons(head: X, tail: BadList) // warning: no instances + + lemma NeverGetHere(xs: BadList) + ensures false + { + NeverGetHere(xs.tail); + } +} diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939b.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939b.dfy.expect new file mode 100644 index 00000000000..f38e41b4de0 --- /dev/null +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939b.dfy.expect @@ -0,0 +1,5 @@ +git-issue-4939b.dfy(24,11): Warning: because of cyclic dependencies among constructor argument types, no instances of datatype 'BadList' can be constructed +git-issue-4939b.dfy(16,12): Error: variable 'a', which is subject to definite-assignment rules, might be uninitialized here +git-issue-4939b.dfy(18,12): Error: variable 'b', which is subject to definite-assignment rules, might be uninitialized here + +Dafny program verifier finished with 1 verified, 2 errors diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939c.dfy b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939c.dfy new file mode 100644 index 00000000000..815a713da73 --- /dev/null +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939c.dfy @@ -0,0 +1,16 @@ +// RUN: %testDafnyForEachCompiler "%s" -- --general-traits=datatype + +module NoAutoInit { + trait GeneralTrait { } + trait ReferenceTrait extends object { } + + datatype A = A(g: GeneralTrait) + datatype B = B(h: ReferenceTrait) + + method Main() { + // Test that code generation succeeds, even thought "a" and "b" are never defined or used + var a: A := *; + var b: B := *; + print "done\n"; + } +} diff --git a/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939c.dfy.expect b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939c.dfy.expect new file mode 100644 index 00000000000..19f86f493ab --- /dev/null +++ b/Source/IntegrationTests/TestFiles/LitTests/LitTest/git-issues/git-issue-4939c.dfy.expect @@ -0,0 +1 @@ +done diff --git a/docs/dev/news/4997.fix b/docs/dev/news/4997.fix new file mode 100644 index 00000000000..9f4163953c2 --- /dev/null +++ b/docs/dev/news/4997.fix @@ -0,0 +1 @@ +Allow a datatype to depend on traits without being told "datatype has no instances"