Skip to content

Add type restrictions to io #15698

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

Vici37
Copy link
Contributor

@Vici37 Vici37 commented Apr 22, 2025

This is the output of compiling cr-source-typer and running it with the below incantation:

CRYSTAL_PATH="./src" ./typer spec/std_spec.cr \
  --error-trace --exclude src/crystal/ \
  --stats --progress \
  --union-size-threshold 2 \
  --ignore-private-defs \
  --ignore-protected-defs \
  src/io

This is related to #15682 .

I also reran the above command but without the --union-size-threshold 2, and updated several long union types to IO by itself.

src/io.cr Outdated
@@ -1146,7 +1146,7 @@ abstract class IO
end

# Same as `pos`.
def tell
def tell : Int64 | Int32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This look very odd to me. Especially as pos is noreturn. Where does this union type come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see Memory#pos return Int32 and Buffered#pos return Int64. The side effect being this wrapping def indirectly inherits the return types of those methods 🤷

Maybe better to omit the return type restriction in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either that, or declare it to be typeof(pos), if that is possible 🤔

Comment on lines 81 to 83
def blocking=(value : Bool) : Int32?
self.system_blocking = value
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These combination of input and return types should not be correct. The return type of an assignment should be the value assigned. Does system_blocking= do anything weird?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

system_blocking= may leak the return value of fcntl.
We should probably return value (c.f. #10083).

For this PR, I'd suggest to just set the return type restriction to Nil.

Copy link
Contributor Author

@Vici37 Vici37 Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #10083, I could also update this method to return Bool and put value at the end?

Edit: Scratch that, I'll just do what you suggest in this case, and let that issue take care of this :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's best to keep this PR focused on adding type restrictions, without changing anything else.

@@ -12,7 +12,7 @@ class IO
EncodingOptions.check_invalid(invalid)
end

def self.check_invalid(invalid) : Nil
def self.check_invalid(invalid : Symbol?) : Nil
Copy link
Contributor

@ysbaddaden ysbaddaden May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought (non-blocking): we should refactor to use an enum instead of a symbol. That would validate the value at comptime, instead of delaying to runtime, and it would be backward compatible, since symbols are automatically transformed into enums.

class IO
  struct EncodingOptions
    enum Invalid
      Skip
    end

    getter name : String
    getter invalid : Invalid?

    def initialize(@name : String, @invalid : Invalid?)
    end
  end

Copy link
Contributor

@ysbaddaden ysbaddaden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still a few details!

self.system_close_on_exec = value
end

def self.fcntl(fd, cmd, arg = 0)
Crystal::System::FileDescriptor.fcntl(fd, cmd, arg)
end

def fcntl(cmd, arg = 0)
def fcntl(cmd : Int32, arg : Int32 = 0) : Int32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why restrict the types to Int32 for the instance method, but not the class method above?

Also, the type should probably be Int. There's no telling what the LibC definitions are using, and the Crystal::System methods are untyped anyway.

@@ -267,7 +267,7 @@ class IO::FileDescriptor < IO
system_tty?
end

def reopen(other : IO::FileDescriptor)
def reopen(other : IO::FileDescriptor) : File
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The File result type is wrong. We only happen to use File in specs, but we return other that is actually an IO::FileDescriptor (or a descendant such as File). Since type annotations don't coerce the value, the method will still return a File or an IO::FileDescriptor (verified).

Suggested change
def reopen(other : IO::FileDescriptor) : File
def reopen(other : IO::FileDescriptor) : IO::FileDescriptor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, great catch!

@@ -146,7 +146,7 @@ class IO
end
end

def read_utf8(io, slice) : Int32
def read_utf8(io : IO, slice : Slice(UInt8)) : Int32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: maybe Bytes?

Suggested change
def read_utf8(io : IO, slice : Slice(UInt8)) : Int32
def read_utf8(io : IO, slice : Bytes) : Int32

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants