You're missing a catch (or an outer 'throws' function) in your example, so SILGen does not know where to jump if f.bar throws. This suggests the code in TypeCheckError is not working right yet.
I would be open to moving the lvalue checking back to Sema from SILGen, as long as the corresponding code in SILGen went away. We used to have this calculated in two places, with the SILGen version being authoritative, and the code in Sema only used in a couple of places, which I think was a bad design.
So it seems like I have got it working for the most part - weird thing is that when I pass -emit-sil or -emit-ir to swiftc, I get the output code. However, when I pass -emit-executable, it crashes with that same SILGen error. When I just call swift <filename>, it compiles and runs and crashes as expected. Any ideas why this is happening?
Example:
extension String: Error {}
struct Foo {
var bar: String {
mutating get throws {
throw "Throwing from a getter"
}
mutating set throws {
throw "Throwing from a setter"
}
}
}
func foo(f: inout Foo) throws {
try f.bar = ""
}
var instance = Foo()
try foo(f: &instance)
When calling ./swift file_name: Fatal error: Error raised at top level: "Throwing from a setter"
When calling ./swiftc -emit-executable file_name: Assertion failed: (ThrowDest.isValid() && "calling emitThrow with invalid throw destination!")
When calling ./swiftc -frontend -emit-ir: OK
When calling ./swiftc -frontend -emit-bc file_name -o file_name.bc) and running using lli: lli -load=/Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftCore.dylib -load=/Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswiftSwiftOnoneSupport.dylib file_name.bc: Fatal error: Error raised at top level: "Throwing from a setter"
Interesting... seems like the crash either happens or not depending on how I invoke the compiler.
So, the SIL looks fine to me and as I said above, it does compile and work. Still trying to work out why it would cause SILGen to crash when calling swift but not swiftc. Or maybe I am doing something wrong when calling the compiler? cc @Slava_Pestov@John_McCall
I think I have figured this out - seems like it's happening due to a synthesised coroutine accessor. synthesizeCoroutineAccessorBody() synthesises a body for the modify accessor yielding an InOutExpr, however it creates an infinite loop in SILGen if the setter throws. For some reason, this doesn't happen when I call swiftc -frontend -emit-sil but it does happen when I call swift -emit-sil. I don't even see the modify accessor in the output SIL code, so I guess perhaps its not needed if the accessor does not have storage? We can then skip synthesising a body:
if (!accessor->getStorage()->hasStorage()) { return; }
A little off-topic: I would love to see a document or a very detailed blog post of your experience starting contribution to the Swift compiler, please include all the gotcha moments you had. Something from zero to hero like is always interesting to follow as Iâm kind of sitting in the same boat right now and reading a C++ book to catch up with that before diving into the compiler itself.
Still needs a bit more work for type checking subscripts correctly, which I am currently working on (this one is a bit tricky, but I'm close). What else needs to be done? Everything else seems to be working fine (compiles, runs, throws the error as expected) according to my tests, but unsure if I am missing some scenarios. @John_McCall@Slava_Pestov
The main thing you need to do for type-checking is to resurrect an old piece of code we had that stored an AccessKind on l-value expressions. It was removed here. That should make it straightforward to figure out what accessors are being used.
That's probably something you should go ahead and prepare a PR for instead of leaving on your feature branch.
Can you share a link to the WIP PR please? Since I haven't seen protocol related examples in this thread, I would like to know if in your tests you already created them.
protocol P {
subscript(a: Int) -> Int { get throws }
subscript(b: Int) -> Int { get throws set }
subscript(c: Int) -> Int { get set throws }
var a: Int { get throws }
var b: Int { get throws set }
var c: Int { get set throws }
}
Can a subscript and therefore get/set actually rethrow if it's argument was a throwing function?
Example:
struct S {
subscript(x: () throws -> Int) -> String {
get rethrows {
return "\(try x())"
}
}
}
@John_McCall would it be a requirement for this patch to make internal _read and _modify (or how they were called) also throwable or can this be easily a follow-up patch?
Not trying to make anything harder for @suyashsrijan, just asking for clarification, since I'm tracking this patch.
The feature is not in any way complete without _read and _modify support. We automatically create _modify accessors for non-final storage in classes as well as storage that satisfies a mutable protocol requirement.
Protocols with throwing accessors work although there's an issue where a throwing getter can fulfil the requirement of a non-throwing getter (as defined in protocol). rethrows doesn't get parsed, but unsure if it's something we want to add right now.
In the sense of this post (which is not available in Swift right now and is not even decided yet) I would expect that any get/set (read/modify) throws all the time. If omitted then it throws Never and therefore works like in the current Swift version.
I would not worry about rethrows. It's useful with subscripts that take closures, especially autoclosures, but it is definitely not a minimum requirement.
Thanks @John_McCall, I have got the type checking working now for subscripts I am using a custom ASTVisitor approach and it works fine. I have a PR ready that reverts the commit you mentioned. Do you think it's worth reverting or should I stick to my approach?
Are you actually handling inout arguments and potentially-mutating base accesses correctly? Itâs certainly possible to do these things with a visitor, but my concern is that youâre going to be âapproximately correctâ.