-
Notifications
You must be signed in to change notification settings - Fork 268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Account for the optional equality support of map/seq inside newtypes #5980
Changes from all commits
07fd118
8dcde42
59b1df8
caad6dd
a66a597
f8842cb
52c304a
fe10780
9e03366
3082e36
c938b2b
d7b7f07
ec2ab42
f3cbb81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/bin/bash | ||
|
||
# Check for required environment variables | ||
if [ -z "$DIR" ]; then | ||
echo "Error: DIR environment variable is not set." | ||
exit 1 | ||
fi | ||
|
||
if [ -z "$name" ]; then | ||
echo "Error: name environment variable is not set." | ||
exit 1 | ||
fi | ||
|
||
# Set the default build flag | ||
NO_BUILD=${NO_BUILD:-false} | ||
|
||
# Locate files matching the specified pattern | ||
files=$(cd "${DIR}/Source/IntegrationTests/TestFiles/LitTests/LitTest" && find . -type f -wholename "*$name*" | grep -E '\.dfy$') | ||
|
||
if [ -z "$files" ]; then | ||
echo "No files found matching pattern: $name" | ||
exit 1 | ||
else | ||
count=$(echo "$files" | wc -l) | ||
echo "$files" | ||
echo "$count test files found." | ||
for file in $files; do | ||
filedir=$(dirname "$file") | ||
( | ||
cd "${DIR}/Source/IntegrationTests/TestFiles/LitTests/LitTest/$filedir" || exit | ||
|
||
build_flag="" | ||
[ "$NO_BUILD" = "true" ] && build_flag="--no-build" | ||
|
||
dotnet run $build_flag --project "${DIR}/Source/Dafny" -- ${action} "$(basename "$file")" | ||
) | ||
done | ||
fi |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
# Ensure name is provided | ||
if [ -z "$name" ]; then | ||
echo "Syntax: make test name=<integration test filter> [update=true] [build=false]" | ||
exit 1 | ||
fi | ||
|
||
# Default values for update and build | ||
update_flag=${update:-false} | ||
build_flag=${build:-true} | ||
|
||
# Set the integration tests folder | ||
integration_tests_dir="${DIR}/Source/IntegrationTests" | ||
lit_tests_dir="${integration_tests_dir}/TestFiles/LitTests/LitTest" | ||
|
||
# Handle no-build logic | ||
if [ "$build_flag" = "false" ]; then | ||
echo "" | ||
echo "Build is disabled. Copying test files to the output directory..." | ||
|
||
framework_dir=$(find "${integration_tests_dir}/bin/Debug" -maxdepth 1 -type d -name "net*" | sort | tail -n 1) | ||
if [ -z "$framework_dir" ]; then | ||
echo "Error: Could not find target framework directory in bin/Debug. Please run at least once with build=true." | ||
exit 1 | ||
fi | ||
output_dir="${framework_dir}/TestFiles/LitTests/LitTest" | ||
|
||
# Find and copy all matching files to the output directory | ||
files=$(cd "$lit_tests_dir" && find . -type f -regex '.*\.\(check\|dfy\|expect\)' -wholename "*$name*") | ||
if [ -z "$files" ]; then | ||
echo "No files found matching pattern: $name" | ||
exit 1 | ||
fi | ||
|
||
# Create output directory if it doesn't exist | ||
mkdir -p "$output_dir" | ||
|
||
# Copy files | ||
echo "$files" | while IFS= read -r file; do | ||
echo "Copying $file to $output_dir" | ||
cp "$lit_tests_dir/$file" "$output_dir/$file" | ||
done | ||
fi | ||
|
||
# Check if update flag is true | ||
if [ "$update_flag" = "true" ]; then | ||
echo "" | ||
echo "Update mode enabled." | ||
echo "Going to update the .expect files to match the current Dafny output." | ||
fi | ||
|
||
# Run dotnet test | ||
echo "Running integration tests..." | ||
DAFNY_INTEGRATION_TESTS_UPDATE_EXPECT_FILE="$update_flag" \ | ||
dotnet test "$integration_tests_dir" \ | ||
$( [ "$build_flag" = "false" ] && echo "--no-build" ) \ | ||
--filter "DisplayName~$name" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,13 +96,21 @@ public Type RhsWithArgument(List<Type> typeArgs) { | |
public TopLevelDecl AsTopLevelDecl => this; | ||
public TypeDeclSynonymInfo SynonymInfo { get; set; } | ||
|
||
public TypeParameter.EqualitySupportValue EqualitySupport { | ||
private IndDatatypeDecl.ES equalitySupport = IndDatatypeDecl.ES.NotYetComputed; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Rust compiler will also need to have precise equality support, as it's the only backend that needs to distinguish between type-parameter conditional equality and no equality possible. |
||
|
||
public IndDatatypeDecl.ES EqualitySupport { | ||
get { | ||
if (this.BaseType.SupportsEquality) { | ||
return TypeParameter.EqualitySupportValue.Required; | ||
} else { | ||
return TypeParameter.EqualitySupportValue.Unspecified; | ||
if (equalitySupport == IndDatatypeDecl.ES.NotYetComputed) { | ||
var thingsChanged = false; | ||
if (ModuleResolver.SurelyNeverSupportEquality(BaseType)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the types are topologically sorted, this will only be executed when the types this newtype depends on have been resolved. |
||
equalitySupport = IndDatatypeDecl.ES.Never; | ||
} else { | ||
ModuleResolver.DetermineEqualitySupportType(BaseType, ref thingsChanged); | ||
equalitySupport = IndDatatypeDecl.ES.ConsultTypeArguments; | ||
} | ||
} | ||
|
||
return equalitySupport; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -376,7 +376,7 @@ public override string TypeName(DafnyOptions options, ModuleDefinition context, | |
|
||
public override bool SupportsEquality { | ||
get { | ||
if (ResolvedClass is ClassLikeDecl { IsReferenceTypeDecl: true } or NewtypeDecl) { | ||
if (ResolvedClass is ClassLikeDecl { IsReferenceTypeDecl: true }) { | ||
return ResolvedClass.IsRevealedInScope(Type.GetScope()); | ||
} else if (ResolvedClass is TraitDecl) { | ||
return false; | ||
|
@@ -400,6 +400,12 @@ public override bool SupportsEquality { | |
i++; | ||
} | ||
return true; | ||
} else if (ResolvedClass is NewtypeDecl newtypeDecl) { | ||
if (newtypeDecl.IsRevealedInScope(Type.GetScope())) { | ||
return newtypeDecl.RhsWithArgument(TypeArgs).SupportsEquality; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because of my change, we could also do the same treatment as for the datatypes, if you preferred. I'm fine with both approaches and to switch if you prefer. |
||
} else { | ||
return false; | ||
} | ||
} else if (ResolvedClass is TypeSynonymDeclBase) { | ||
var t = (TypeSynonymDeclBase)ResolvedClass; | ||
if (t.SupportsEquality) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2861,11 +2861,7 @@ void MarkSCCAsNotSupportingEquality() { | |
return; // we are done | ||
} | ||
foreach (var arg in ctor.Formals) { | ||
var anotherIndDt = arg.Type.AsIndDatatype; | ||
if (arg.IsGhost || | ||
(anotherIndDt != null && anotherIndDt.EqualitySupport == IndDatatypeDecl.ES.Never) || | ||
arg.Type.IsCoDatatype || | ||
arg.Type.IsArrowType) { | ||
if (arg.IsGhost || SurelyNeverSupportEquality(arg.Type)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need this type traversal to be recursive, otherwise soundness issue 5972c.dfy |
||
// arg.Type is known never to support equality | ||
MarkSCCAsNotSupportingEquality(); | ||
return; // we are done | ||
|
@@ -2884,8 +2880,7 @@ void MarkSCCAsNotSupportingEquality() { | |
} | ||
foreach (var ctor in dt.Ctors) { | ||
foreach (var arg in ctor.Formals) { | ||
var type = arg.Type; | ||
DetermineEqualitySupportType(type, ref thingsChanged); | ||
DetermineEqualitySupportType(arg.Type, ref thingsChanged); | ||
} | ||
} | ||
} | ||
|
@@ -2925,30 +2920,75 @@ void MarkSCCAsNotSupportingEquality() { | |
} | ||
} | ||
|
||
private static void DetermineEqualitySupportType(Type type, ref bool thingsChanged) { | ||
public static bool SurelyNeverSupportEqualityTypeParameters(IndDatatypeDecl.ES equalitySupport, List<TypeParameter> typeParams, List<Type> typeArgs) { | ||
return | ||
equalitySupport == IndDatatypeDecl.ES.Never || | ||
(equalitySupport == IndDatatypeDecl.ES.ConsultTypeArguments && | ||
typeArgs.Zip(typeParams).Any(tt => | ||
tt.Item2.NecessaryForEqualitySupportOfSurroundingInductiveDatatype && SurelyNeverSupportEquality(tt.Item1))); | ||
} | ||
|
||
// If returns true, the given type never supports equality | ||
// If return false, then the type must support equality if type parameters support equality | ||
// It is unsound for a type to make this function return false when there is no type parameter | ||
// assignment that makes this type support equality | ||
public static bool SurelyNeverSupportEquality(Type type) { | ||
type = type.NormalizeExpand(); | ||
return | ||
type.AsNewtype is { EqualitySupport: var equalitySupport, TypeArgs: var typeParams } | ||
&& SurelyNeverSupportEqualityTypeParameters(equalitySupport, typeParams, type.TypeArgs) | ||
|| | ||
type.AsIndDatatype is { EqualitySupport: var equalitySupport2, TypeArgs: var typeParams2 } | ||
&& SurelyNeverSupportEqualityTypeParameters(equalitySupport2, typeParams2, type.TypeArgs) | ||
|| | ||
type.IsCoDatatype || type.IsArrowType || | ||
type.AsSeqType is { Arg: var argType } && SurelyNeverSupportEquality(argType) || | ||
type.AsMapType is { Range: var rangeType } && SurelyNeverSupportEquality(rangeType); | ||
} | ||
|
||
public static void DetermineEqualitySupportType(Type type, ref bool thingsChanged) { | ||
if (type.AsTypeParameter is { } typeArg) { | ||
typeArg.NecessaryForEqualitySupportOfSurroundingInductiveDatatype = true; | ||
thingsChanged = true; | ||
} else if (type.AsMapType is { } typeMap) { | ||
DetermineEqualitySupportType(typeMap.Range, ref thingsChanged); | ||
} else if (type.AsSeqType is { } typeSeq) { | ||
DetermineEqualitySupportType(typeSeq.Arg, ref thingsChanged); | ||
} else if (type.AsIndDatatype is { } otherDt) { | ||
if (otherDt.EqualitySupport == IndDatatypeDecl.ES.ConsultTypeArguments) { // datatype is in a different SCC | ||
var otherUdt = (UserDefinedType)type.NormalizeExpand(); | ||
var i = 0; | ||
foreach (var otherTp in otherDt.TypeArgs) { | ||
if (otherTp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype) { | ||
var tp = otherUdt.TypeArgs[i].AsTypeParameter; | ||
if (tp != null) { | ||
tp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype = true; | ||
thingsChanged = true; | ||
} | ||
} else if (type.Normalize() is UserDefinedType userDefinedType) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need this normalization? I thought that AsMapType and AsSeqType would actually do that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a type synonym does not have an explicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An explicit |
||
if (userDefinedType.ResolvedClass is TypeSynonymDeclBase typeSynonymDecl) { | ||
if (typeSynonymDecl.IsRevealedInScope(Type.GetScope())) { | ||
if (typeSynonymDecl.Characteristics.EqualitySupport == TypeParameter.EqualitySupportValue.Required) { | ||
return; // It's guaranteed that this type synonym requires equality | ||
} | ||
DetermineEqualitySupportType(typeSynonymDecl.RhsWithArgument(userDefinedType.TypeArgs), ref thingsChanged); | ||
} | ||
} else if (userDefinedType.ResolvedClass is NewtypeDecl newtypeDecl) { | ||
if (newtypeDecl.IsRevealedInScope(Type.GetScope())) { | ||
DetermineEqualitySupportType(newtypeDecl.RhsWithArgument(userDefinedType.TypeArgs), ref thingsChanged); | ||
} | ||
} else if (userDefinedType.ResolvedClass is IndDatatypeDecl otherDt) { | ||
if (otherDt.EqualitySupport == IndDatatypeDecl.ES.ConsultTypeArguments) { | ||
// datatype is in a different SCC | ||
var otherUdt = (UserDefinedType)type.NormalizeExpand(); | ||
var i = 0; | ||
foreach (var otherTp in otherDt.TypeArgs) { | ||
if (otherTp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype) { | ||
var tp = otherUdt.TypeArgs[i].AsTypeParameter; | ||
if (tp != null) { | ||
tp.NecessaryForEqualitySupportOfSurroundingInductiveDatatype = true; | ||
thingsChanged = true; | ||
} | ||
} | ||
|
||
i++; | ||
i++; | ||
} | ||
} | ||
} | ||
} else if (type.AsMapType is { } typeMap) { | ||
// A map type's Domain type is required to support equality (just like the argument type for sets and multisets), but | ||
// it is optional for the map type's Range type to support equality. Thus, we need to determine the equality support | ||
// for just the Range type. | ||
DetermineEqualitySupportType(typeMap.Range, ref thingsChanged); | ||
} else if (type.AsSeqType is { } typeSeq) { | ||
// Like the Range type of a map, it is optional for a sequence type's argument type to support equality. So, we make a call | ||
// to determine it. | ||
DetermineEqualitySupportType(typeSeq.Arg, ref thingsChanged); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes to the makefile make it much much easier to test single files without having to rebuild, and also to update individual outputs without having to run the IDE or a complex command (or Lit, which we are not always supporting)