The block passed to sig is evaluated in a different context, one where the local object has those methods. The methods aren't added globally, which is why you need the first method to switch the context. It's generally a good policy to avoid adding those class methods at the top level, which Sorbet does assiduously.
Sig also turns into a no-op if you have runtime verification turned off, which is another good reason not to call params right away, because (in ruby) you can ignore everything in the block if the block is not called, sort of like a debug macro in C, but you can't do that with a method that is called - it must evaluate its parameters.
That's super informative, thank you! Can you also add a word of explanation as to how the `sig` call gets associated with the method that follows it? What ties them together, is there some static parser?
Sorbet has its own written-in-C++ parser that does the actual parsing. At runtime, the sig call basically sets a flag for the next method that is defined which hooks into it to validate that the parameters passed in are as defined, and that the return value is as expected. I believe they're delving into the dark magic in the interpreter directly, the docs are here: https://sorbet.org/docs/runtime
For the runtime, there is not much dark magic. Each type that has an `extend T::Sig` has `method_added` hooks registered, which notifies Sorbet runtime whenever a method is defined on the type. When that `method_added` hook is called, Sorbet runtime uses the sig flag that you mention to associate the `sig` with the method definition that follows it.
The actual signature checking is complicated, but associating the sig call with the method is pretty simple in Ruby — you just hook `method_added`. (Of course the actual implementation in something like Sorbet is a lot more sophisticated than just `def method_added`, but that's the basics of how you make a method call alter the next method definition.)
Sig also turns into a no-op if you have runtime verification turned off, which is another good reason not to call params right away, because (in ruby) you can ignore everything in the block if the block is not called, sort of like a debug macro in C, but you can't do that with a method that is called - it must evaluate its parameters.