-
-
Notifications
You must be signed in to change notification settings - Fork 742
Fix issue# 10717. #1436
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 issue# 10717. #1436
Conversation
Why does void foo(dchar){}
void main()
{
uint d = uint.max;
foo(d);
} That said, I'm unsure about the the uint to dchar cast. Is the fact it is accepted a bug? void main()
{
dchar f = uint.max; //Illegal
uint d = uint.max;
dchar e = d; //But this is OK?
} |
The purpose of accepting The As for your example with |
OK. Your explanation makes sense. As for the whole |
Okay. I updated it so that |
Implemented as: auto toUpper(C)(C c)
if (is(C : dchar))
out(result)
{
assert(!isLower(result));
}
body
{
alias Ret = Select!(isScalarType!C, C, dchar);
return cast(Ret)(c - ('a' - 'A'));
} Helps keep the public interface cleaner, and the cognitive overhead down. This is always desired (IMO). |
One of the things that I noticed is that this behavior just changed: string s = "hello";
foreach(ref e; s)
{
auto a = toUpper(e);
static assert(is(typeof(a) == char));
} Never mind that a is a Select!(isScalarType!C, Unqual!C, dchar); (or equivalent of course) I don't see much point in returning a built-in value type as const/immutable anyways. As for "shared", I think it becomes outright wrong? |
A third comment: This changes a non-template function into a template function, just because of the return type, yet the implementation remains unchanged. This might be premature optimization (or even non-optimization due to the size of said function), but maybe it should be implemented as a forward to a non-template function? EG: //Public template that only filters and changes return type
auto toUpper(C)(C c) @safe pure nothrow
if (is(C : dchar))
out(result)
{
assert(!isLower(result));
}
body
{
alias Ret = Select!(isScalarType!C, C, dchar);
return cast(Ret) toUpperImpl(c);
}
//Non template that does actual job.
private dchar toUpperImpl(dchar c) @safe pure nothrow
{
return isLower(c) ? cast(dchar)(c - ('a' - 'A')) : c;
} I don't know. I don't think it makes much of a change either way, so either way is fine for me. |
Okay, update per your excellent suggestions. I had made the overloads separate primarily because of wanting to mark the overload for the built-in types with |
Hum... I thought I learned One of the issues with your re-entering approach is that the output contract get validated twice, which is sub-optimal for non-release builds. It's not a big deal, but I guess we should avoid it if we can. |
characters, and all $(D toX) functions do nothing to unicode characters. | ||
All of the functions in std.ascii accept Unicode characters but effectively | ||
ignore them. All $(D isX) functions return $(D false) for Unicode | ||
characters, and all $(D toX) functions do nothing to Unicode characters. |
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.
If we want to get technical, these functions will ignore unicodes outside of the Basic Latin (EG ASCII) Block. ASCII characters are still Unicode characters.
Maybe re-writing this in the form of "Will accept any unicode charater, but ignore those that aren't ASCII" would be more accurate?
Well, if I knew about it previously, I'd completely forgotten about it since.
Then I'll just remove the contract and avoid the extra code. I don't particularly like |
This makes it so that if you're operating on chars or wchars with std.ascii functions, you don't have to cast the return values back to char or wchar, but it still allows you to operate on dchars without mangling any Unicode values.
Okay. Updated. |
I expected you'd do that. I don't really think that was meant to be in an out contract either anyways. As far as I'm concerned, this pull is ready to go. I'll let it sit for a couple of days, just in case, and then I'll merge it. |
If you're going to have an out contract for |
We're going to go off topic now, but in any case, the way I understood it, a "contract" is meant to validate values at the boundaries of a function, that may be wrong, but through no fault of the actual function. For example, taking the classic "sqrt" function, this is the "I work fine on positive numbers. The contract here, is you don't feed me negative numbers, those are outside of my legal range". An out contract, (again, the way I understood it), is to validate output that could be wrong due to user input. For example, if you write "POW(int)", then the contract could be "I work fine, as long as the input you give me won't cause my output to overflow". I'm definitely not arguing that the contract implementations were wrong, just that, as you said, that whole out contract had no business being there in the first place. That was the stuff of unittests. |
I think that you misunderstand the purpose of out contracts blocks, much as you seem to have the basic idea of DbC correct (though it could be that we both understand perfectly well and just aren't quite communicating it properly). The purpose of the in contract block is to check that the input is legal according to the "contract" of the function - what the function expects and requires. The function - per the contract - then guarantees that it will generate the correct output for that input. The purpose of the out contract block is to verify that the function kept up its part of the bargain and that the output is therefore correct for the given input. The out contract block doesn't care whether the input was valid or not, because the in contract block should have already caught all of the cases where the input was invalid. It assumes that the input was valid and is just verifying that the output is correct. The "perfect" out contract block would throw an As such, in the case of And if the output of |
|
||
static assert(isSafe!(toUpper!char)); | ||
static assert((functionAttributes!((){'a'.toUpper();}) & FunctionAttribute.pure_) != 0); | ||
static assert((functionAttributes!((){'a'.toUpper();}) & FunctionAttribute.nothrow_) != 0); |
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.
Just like for is
, you can omit the ()
in front of the block/lambda here. EG:
is(typeof({T t = t;}));
or
if (functionAttributes!({'a'.toUpper();}) != FunctionAttribute.pure_)
Also, I wouldn't complain about a != 0
in an if, but it is adding an extra pair of parenthesis.
I'll merge this evening regardless.
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.
functionAttributes!({'a'.toUpper();}) != FunctionAttribute.pure_
would be wrong, because the result of functionAttributes
is a bunch of |
ed flags, and pure_
is just one of them.
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.
I don't understand what you are saying. I think there might have been a misunderstanding. Are you saying this is wrong?
static assert(functionAttributes!({'a'.toUpper();}) & FunctionAttribute.safe);
static assert(functionAttributes!({'a'.toUpper();}) & FunctionAttribute.pure_);
static assert(functionAttributes!({'a'.toUpper();}) & FunctionAttribute.nothrow_);
It works on my machine...?
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.
No. That will work. What won't work is if (functionAttributes!({'a'.toUpper();}) != FunctionAttribute.pure_)
, because in that example you're comparing the result of functionAttributes
against pure_
, whereas in your last example, you're checking that the result of &
on pure_
is non-zero.
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.
Ooh... right '-_- . I see it now. That above test was non-sense :D I missed that I changed &
into a !=
.
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.
By the way, while I'm not necessarily against not having the != 0
, I would point out that it does help avoid someone thinking that the &
should have been a &&
. Closer inspection of the expression should make it fairly clear anyway, but that's always a risk when using &
or |
in boolean expressions.
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.
That's a good point too. I'm fine with leaving it the way it is too. As you want of course.
This makes it so that if you're operating on chars or wchars with
std.ascii functions, you don't have to cast the return values back to
char or wchar, but it still allows you to operate on dchars without
mangling any Unicode values.