-
Notifications
You must be signed in to change notification settings - Fork 271
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
Incorrect printing of generically-typed strings in Go (a.k.a. should string
!= seq<char>
?)
#2582
Comments
Very nice investigation 😮 For the record, our documentation considers this a TODO for go (https://dafny.org/dafny/Compilation/Go.html):
|
I neglected to mention another option: align on the Java solution of always providing a |
I'm in favor of (keeping
I propose:
More specifically, "print as a string" means printing each character as a printable character, without any separator between the characters and without any enclosing brackets or double quotes. This seems like what you want when you're knowingly giving
(with a newline at the end). In the When printing the field of a datatype, the "static, declared type" refers to the type of the field in the datatype D<X> = D(X, string) the statement
|
Thanks @RustanLeino! I like your proposal even though it is a breaking change and a step backwards in some runtimes. I think making the runtimes consistent and avoiding the cost/complexity of tracking this RTTI is worth it. Just to clarify, your // Independent semantics for each argument to print statments
method Print<T>(t: T) {
if T == string {
print t as string;
} else {
print ToString(t);
}
}
// Similar to __repr__ in Python: meant to produce Dafny expression syntax
// All runtimes have something equivalent to this.
function method ToString<T>(t: T) {
if T == string then
'"' + t as string + '"'
else if T == seq<E> then
'[' + CommaSeparated(Seq.Map(ToString, t as seq<T>)) + ']'
else if T == D<X> then
var D(x, s) := t as D<X>;
// X here might be a concrete type like string, or could be a type parameter
// and therefore not provide any help to ToString<T>.
'D(' + ToString<X>(x) + ', ' + ToString<string>(s) + ')'
else (etc.)
} Just pointing out that we seem to need special casing of Is this correct as well? datatype D<X> = D(X, string)
method Main() {
var d: D<string> := D("hello", "there");
print d; // prints D("hello", "there"), since X is statically known to be string?
} If so, it implies that we need to thread a type descriptor through |
It seems surprising that a change in a static type, which doesn't cause a type error, influences runtime behavior. Does that occur anywhere else in the language? Have we considered having one printing option where the static type must be a For example: var x := [1,2,3];
var y := "hi";
print x; // Error, x is not a seq<char>
print y; // Accepted, prints "hi"
print stringify(x); // Accepted, prints "[1, 2, 3]"
print stringify(y); // Accepted, prints "['h', 'i']" That's more verbose and I guess @RustanLeino you're argueing that @RustanLeino are you also saying you expect us to keep a dedicated |
But why? Surely printing |
I think the suggestion is to have print work like this:
I think because it only requires implementing a single runtime type based print function. You could generate a print function based on the type |
Ah! Hmm. Maybe that fine then ^^ |
I've pulled in implementing this change as part of dafny-lang/rfcs#13, so this breaking change is part of the larger breaking change controlled by |
One consequence of this design I'm only seeing now that I'm implementing it: we are avoiding the need to maintain runtime information to distinguish method Print<T>(t: T) {
print t, "\n";
} We have been implying that I still think it's a very good idea to make |
@robin-aws Good catch. This is a bummer. I think this is where |
Agreed it's a bummer, but I'm still leaning towards making the proposed behavior work. It's at least tractable to implement in all the backends, just harder to implement efficiently in some. Allowing the backend to choose the most convenient behavior means we are regressing in terms of consistency across backends. By my testing |
@RustanLeino I've found a good solution for Java that seems to work well with no runtime overhead, and wanted to write it down here in case someone else wants to use it to solve the signed integer problem later. We use primitive types like This requires the java compiler code to augment There's an added benefit of increased type safety in the generated code as well, preventing us from accidentally mixing up an |
A late response to something earlier in this thread. In what I proposed, I had not intended what @robin-aws asked about on Aug 12. In particular, I had intended for the Instead, I had intended what @keyboardDrummer had said on Aug 15, namely that print would behave like this:
Regarding Java, I do like @robin-aws 's suggestion of using our own boxing type for things like (our) |
I didn't see this response until after merging #3016, but let me respond in case we want to tweak the feature before releasing.
That's good because I didn't end up implementing that per se. :) I did end up something close that only relied on static type information at the point of declaration, similar to the top-level case for print Some("hi").value; // prints "hi"
print Some("hi"); // prints "Some(['h','i'])" The third is slightly different: print OnlyContainsString("hi"); will produce If you still have concerns @RustanLeino let me know. Otherwise I'll close this issue as |
Everything above was implemented as part of |
This prints
Hello, World!
in every target language except for Go, which prints[H, e, l, l, o, , W, o, r, l, d, !]
.The issue is that
string
is just an alias forseq<char>
in Dafny, but we have a special case of implicit parametric type specialization in Dafny, whereprint
-ing a string should look different than a generic sequence.Each backend implements this in almost completely unique ways:
if (typeof(T) == typeof(char))
TypeDescriptor
valuesstring
instead of adafny.Seq
(leading to other issues)seq<char>
(which is why it fails on this example)str
value), which is the only reason it passes this example.operator<<
for aDafnySequence<char>&
.@RustanLeino has suggested that one solution would be to align on Go's approach, such that a sequence value only prints like a string if the static type of the value is
seq<char>
. This would be a step backwards in some cases, but seems reasonable given thatprint
is much more of a debugging tool than a rigorous I/O feature, and hence best-effort support that is 100% consistent and simple to implement in each runtime is appealing.Another option is to make
string
a distinct type fromseq<char>
, given that we are already planning on making breaking changes related to strings to provide better Unicode support (dafny-lang/rfcs#13). At a minimum we could havestring
act like anewtype
based onseq<char>
, so that it would support all the same sequence operations but requiringas
casting to be used as aseq<char>
.Or we could go much further and define
string
as a completely separate built-in type with its own API, possibly NOT including random-access element selection (s[i]
) to allow for the fact that native strings encoded with UTF-8 or UTF-16 cannot directly support random access. I wouldn't recommend this, as it would not only be a much bigger migration pain for users but would also mean a lot more work in the verifier to understand strings and on Dafny users to achieve verification (and will address this in more detail in the RFC).The text was updated successfully, but these errors were encountered: