Coping with misleading error messages of the Swift compiler (context dependence, type inference) - xcode

While the Swift compiler (Xcode 7.2) seems perfectly correct in diagnosing an error for some source text equivalent to the following, it took long to detect the actual error made. Reason: the programmer needs to look not at the text marked, but elsewhere, thus mislead, wondering why an optional string and a non-optional string can not be operands of ??...
struct Outer {
var text : String
}
var opt : String?
var context : Outer
context = opt ?? "abc"
Obviously, the last line should have had context.text as the variable to be assigned. This is diagnosed:
confusion2.swift:9:19: error: binary operator '??' cannot be applied\
to operands of type 'String?' and 'String'
context = opt ?? "abc"
~~~ ^ ~~~~~
The message is formally correct. (I am assuming that type checking the left hand side establishes an expected type (Outer) for the right hand side, and this, then, renders the expression as not working, type-wise.) Taken literally, though, the diagnosis is wrong, as is seen when fixing the left hand side: ?? can be applied to operands of type String? and String.
Now, if this is as good as it gets, currently, in terms of compiler messages, what are good coping strategies? Is remembering
Type inference!
Context!
…
a start? Is there a more systematical approach? A check list?
Update (I'm adding to the list as answers come in. Thanks!)
break statements apart, so as to have several lines checked separately (#vacawama)
Beware of optionals (such as values got from dictionaries), see testSwitchOpt below
Another one
enum T {
case Str(String)
case Integer(Int)
}
func testSwitchOpt(x : T?) -> Int {
switch x {
case .Integer(let r): return r
default: return 0
}
}
The compiler says
optandswitch.swift:8:15: error: enum case 'Integer' not found in type 'T?'
case .Integer(let r): return r
A fix is to write switch x! (or a more cautious let), so as to make type checking address the proper type, I guess.
I could, perhaps should, file some report at Apple, but the issue seems to represent a recurring subject—I have seen this with other compilers—and I was hoping for some general and re-usable hints, if you don't mind sharing them.

Swift's type inference system is great in general, but it can lead to very confusing to outright wrong error messages.
When you get one of these Swift error messages that makes no sense, a good strategy is to break the line into parts. This will allow Swift to return a better error message before it goes too far down the wrong path.
For example, in your case, if you introduce a temporary variable, the real problem becomes clear:
// context = opt ?? "abc"
let temp = opt ?? "abc"
context = temp
Now the error message reads:
Cannot assign value of type 'String' to type 'Outer'

Related

Intellisense with Union Types

I find that intellisense is missing when assigning to a var with a type that is a union type. This makes sense - the compiler doesn't know which of the unioned types you are assigning (although at some point it could deduce when it has enough information but it does not do this either...).
Fine - so I can be explicit and cast the assignment to the type I intend, and the intellisense returns. But this leads to a second problem - for some reason it seems that TypeScript will allow the cast of an empty object literal to any interface, but as soon as a single property is added, the object literal must satisfy the entire interface.
If have two direct questions about this behavior, and they are in the comments in the following code example. I realize I could declare the test vars of more specific types - that is not the point of this topic. Thanks for your help.
interface ITestOne {
a: string;
b?: string;
}
interface ITestTwo {
c: string;
}
type EitherType = ITestOne | ITestTwo;
var test1: EitherType = {}; // ERROR, no intellisense to help fill out the required properties in the object literal
var test2: EitherType = {} as ITestOne; // ALLOWED - Why is this allowed?
var test3: EitherType = { b: 'blah' } as ITestOne; // ERROR: property a is missing. Why ISN'T this allowed if the line above is allowed?
UPDATE 2017-0131
Reply From a bug report I opened on the typescript project on this topic:
What type assertion does, it tells the compiler to "shut up" and trust you. The operator behaves both as an upcast and as a downcast operator. The only check is that the one of the types is assignable to the other.
In the example above, for test: {a: string, b?:string} is assignable to {} (which requires no arguments); for test2 {a: string, b?:string} is assignable to {b:string}, since the type of the only required argument in the target b matches. for test3 neither {a: string, b?:string} is assignable to {b:string, x:string} since it is missing x nor {b:string, x:string} to {a: string, b?:string} since it is missing a.
So, when casting, the source or the target are only verified not to be two completely unrelated types (i.e. number and string), but otherwise the assignment is allowed. My test3 case produced the described result in TypeScript 1.7, but it is now allowed in TypeScript 2.1.
My question about how to get meaningful intellisense in this scenario still stands. However, I suspect the answer is that it is simply not supported without the use of a type guard block.

Swift optionals - warning on conditional cast from 'x' to 'x' always succeeds

I was wondering if there is a way to turn off/avoid 'yellow' warnings in xcode on if let...NSUserDefaults constructs where the key is of a known value.
For example:
if let x = NSUserDefaults.standardUserDefaults().integerForKey("myKey") as? Int {...}
Because of the if let I have to use as?. However, as I am using a known value type (in this case integer) the as? Int is effectively redundant - which is why I am getting the 'yellow warning'.
Thoughts? Is there a better way to code these types of constructs?
My suggestion would be to address the issue instead of silencing the warnings. :)
NSUserDefaults.standardUserDefaults().integerForKey("myKey") does not return an Optional, and the type is known, so you don't need neither optional binding with if let nor type casting with as?.
Just this:
let x = NSUserDefaults.standardUserDefaults().integerForKey("myKey")
will suffice, since .integerForKey just returns 0 if it can't get the actual value.
If you don't like this behavior of getting a default value (I don't), then don't use .integerForKey and use objectForKey with optional binding and type casting instead. Like you were doing first but with .objectForKey replacing .integerForKey. That way you'll get an actual nil if the value for the key is unreachable, not a default value.
if let x = NSUserDefaults.standardUserDefaults(). objectForKey("myKey") as? Int {...}
First of all check always the signature:
⌥-click on the symbol integerForKey: or look at Quick Help.
You will see:
func integerForKey(_ defaultName: String) -> Int
It reveals the return value is a non optional.
Non optionals can retrieved directly as described in Eric's answer without any type casting, optional binding causes an error.
That's one of the essential semantics in Swift.

What's the difference when assigning a value to a field in square brackets LINQ?

I have seen when coding in LINQ that when a value is assigned to a field sometimes is in this way Table["Field"] and any others like this Table.Field but can somebody explain me what's the difference please?
For example when modifying a field:
var ttAbccode_xRow =
(from ttAbccode_Row in ds.ABCCode select ttAbccode_Row).FirstOrDefault();
if (ttAbccode_xRow != null) {
ttAbccode_xRow["PI"] = 3.1416;
}
or
if (ttAbccode_xRow != null) {
ttAbccode_xRow.PI = 3.1416;
}
Accessing field via indexer (square brackets) returns object data type. That means that your compiler cannot detect data types incompatibility. You could assing for example string value (eg. "abcd") and you won't get error at design time, but as late as at runtime.
Second method (if available in your result set) is much more safe. Your property will have proper data type hence compiler will detect data types incompatibility at design time.
If I had both access methods available I would always prefer second one. It is less error prone.

Is Swift type-inference contradicting itself here?

Here's my test code:
var myDict: [String: AnyObject] = ["k":"v"]
var a = myDict["k"]
var b = a as String
var c = myDict["k"] as String
Here's my Swift playground in Xcode6-beta6:
According to the rules of type inference, doesn't complaining about c logically contradict not-complaining about b?
I believe that this is a bug. Part of what is going on here is that String is not an object. If you change the first line to:
var myDict: [String: Any] = ["k":"v"]
then everything is fine. So, given that string is not an object, casting a variable of type AnyObject? to a String should definitely yield an error. And, since the compiler has already decided that a is of type AnyObject? it should complain about casting a to a String.
Note that if you change the last line to:
var c = myDict["k"] as NSString
the error goes away supporting the notion that the issue is that String is not an object. You get the same complaint if you put an Int as the value in the array and try to cast that to an Int.
Update:
So the plot thickens. If you don't import Foundation or import something that imports Foundation, then you get additional errors. Without Foundation:
So clearly some of this has to do with the dual nature of Strings as non-objects and NSStrings as objects and the ability to use Strings as NSStrings when Foundation is imported.
This has to do with the fact that Dictionary has two subscript overloads:
subscript (key: Key) -> Value?
subscript (i: DictionaryIndex<Key, Value>) -> (Key, Value) { get }
The first is the familiar one where you pass a key and it gives you an optional of the value; and you can use to set the value on a key.
The second one is less common. I believe DictionaryIndex is a kind of iterator into the dictionary, and you can use it as a subscript to directly get the key-value pair at that iterator.
When the compiler can't find an overload that matches (in this case, the first one doesn't match because it returns an optional, which cannot be cast to non-optional String), it just picks one arbitrarily (well, it seems arbitrary to me anyway) to show in the error. In this place, it picks the second one, which you don't recognize. That's why the error seems weird to you.
This works.
var c = myDict["k"] as AnyObject! as String // "v"
To answer your question, the reason Swift complains could be that you are trying to do these two conversions in one go. Remember, the statement var a = myDict["k"] contains an implicit conversion already. The implied conversion is AnyObject?, so the above would also work like this:
var c = myDict["k"] as AnyObject? as String // "v"
Note that the above would lead to a run time error if the key "k" where not defined. You would allow this to return nil by casting to String?.

Code style = when to use comparision value first in 'if' statement

I have seen a few examples lately where if statements are written as follows:
if ( false === $testValue) {
//do something
}
as opposed to the more general:
if ($testValue === false) {
//do something
}
Clearly it is a style issue and it has no bearing on the result, but my question is can anyone say why anyone would use this style and where it comes from.
The code examples I have seen with this style have been from seriously good programmers so I dont think its a necessarily a bad style.
It's so that if you accidentally type = (assignment) instead of == (comparison), the compiler complains that the constant cannot be assigned to.
Compare:
if (false = $testValue) {
// does not compile, cannot assign to constant
}
to:
if ($testValue = false) {
// assigns false to $testValue, never evaluates to true
}
The former doesn't compile, the latter does and has a bug.
I used to see checks like this in C and C++:
if (null == x)
The reason for it was that mistyping = for == would make no sense to the compiler, because assignment to null or a constant would be an error.
In something like C++ (I'm not sure what language you're using there with the ===) if you have
if(x == 2), and you write it if(x=2) (so the value is assigned an not checked for equality) this doesn't cause a compiler error (most compilers today will warn you about it), but if you write if (2=x) instead of if(2==x) that will defintely produce an error.
It's to prevent assignment when comparison was intended.
I don't like this style myself but I see it a lot from our Indian developers so maybe it's being taught over there.
It's completely unnecessary if a '0 warnings, 0 errors' build policy was adhered to, and anyone who uses this style is not likely to provide clean building code.

Resources