Crystal: Apply responds_to? type restriction/flow analysis to constants

Created on 2 Jun 2019  Â·  10Comments  Â·  Source: crystal-lang/crystal

I recently wanted to write some code that shims in an old API for Crystal versions below 0.28.0, but works on the newest version too, so I thought I'd use responds_to?.

if STDOUT.responds_to? :"flush_on_newline="
    # Crystal < 0.28.0
    STDOUT.flush_on_newline = false
else
    # Crystal >= 0.28.0
    STDOUT.sync = false
end

However, despite the docs saying that the former branch would be restricted to types that contain that method, the 0.28.0 compiler gives me the following error:

Error in src/main.cr:3: undefined method 'flush_on_newline=' for IO::FileDescriptor

        STDOUT.flush_on_newline = false
               ^~~~~~~~~~~~~~~~

Rerun with --error-trace to show a complete error trace.

In that branch, STDOUT should logically be restricted to no types (i.e. that branch is unreachable), but despite this the compiler expects it to be of the type IO::FileDescriptor.

I might be able to come up with a (less surefire) way to get around this in my particular case, but for convenience and technical correctness I'd wager this is something the compiler should handle.

feature compiler topicsemantic

Most helpful comment

Maybe slight clarification in the docs, or stress on local variables, as I mentioned

All 10 comments

Assigning STDOUT to local variable works as intended though.

Huh, you're right – this works:

out_io = STDOUT
if out_io.responds_to? :"flush_on_newline="
    # Crystal < 0.28.0
    out_io.flush_on_newline = false
else
    # Crystal >= 0.28.0
    out_io.sync = false
end

How come? Does this have to do with STDOUT being a constant?

It has to do with it not being a local variable.

Hmm. Should those two parts of the documentation mention global variables too, then, perhaps?

Honestly it should just say that it's only for local variables, if that's how it is.

While constants could be supported, because they really can't be reassigned in between (or at all 😅), it would be a special case.

I guess this behavior should be extended to support global variables, then! The idea is that it supports variables that can't be reassigned in another thread, not specifically local variables, after all.

To clarify terminology here, Crystal has no global variables, UPPERCASE variables are constants.

I don't think this is worth implementing. You can assign the constant to a local variable and work from there.

Maybe slight clarification in the docs, or stress on local variables, as I mentioned

Was this page helpful?
0 / 5 - 0 ratings