Open devnote-dev opened 1 year ago
Some pseudo-methods like is_a?
accept type names that are not part of regular code (e.g. postfix *
), so they are not "just" methods with additional restrictions.
Assuming you mean things like UInt8*
, I would have thought that resolves to Pointer(UInt8)
in most cases, but my point about the restrictions was primarily the inability to redefine/overload them.
Yes it resolves to ::Pointer(UInt8)
, but normal method or macro calls do not have this capability at all, because syntactically they never expect type names in their argument lists. This is also why some other things like offsetof
cannot be turned into regular methods
The main point is that there are some lax syntax rules around pseudo-methods which should be more restrictive. Also the error messages could be improved.
How to implement that is a different story. Removing some of the special case handling for pseudo-methods could be an option. We should look into that. But it's also clear that they are parsed explicitly for a reason because they have special syntactical features.
.is_a?
seems quite straightforward in the sense that the error message can just be updated to something friendlier. For .as
/.as?
and .nil?
, they allow one or more tokens preceding the method call which would normally raise an invalid arguments count/signature exception at compile time. Is the parser ignoring these tokens, or is something else happening here?
if foo.as? Nil Int32
puts true
end
is equivalent to:
if foo.as?(Nil)
Int32
puts true
end
Same goes for normal methods, actually:
class Foo
def foo(x)
true
end
end
if Foo.new.foo Nil Int32
puts true
end
# equivalent to:
if Foo.new.foo(Nil)
Int32
puts true
end
It's also worth noting that .as
/.as?
, .is_a?
, .nil?
and .responds_to?
all have this issue, but it is only when there are 2 tokens given – any more will raise the appropriate exception.
That too happens for normal methods, since if Foo.new.foo Nil Int32 Bool
becomes if Foo.new.foo(Nil); Int32 Bool
and the final space isn't allowed
@HertzDevil Sorry I didn't see your comment – why is that? Shouldn't a semicolon be expected here?
No idea. For the record an uppercase name can be a method in Ruby, so it is parsed like a method there:
# okay
def Bar(x)
1
end
# okay, equivalent to Foo.new.foo(Bar(String))
# where `Bar(String)` is a method call, not a generic
if Foo.new.foo Bar String
puts true
end
The issue about statement end after an if
condition is similar to https://github.com/crystal-lang/crystal/issues/13386. I guess we can expand that to all control structures.
The issue about statement end after an
if
condition is similar to #13386.
Is there a reason why Crystal does not support if foo then bar end
syntax like Ruby? I tried finding related discussions but searching "if then" in issues doesn't help narrow it down. 😅
Wouldn't that just be bar if foo
? Probably just for sake of keeping things simple? :shrug:.
I suppose it's probably an unnecessary alternative syntax. if foo; bar end
works. There's no need for then
.
I would be in favor of using if..then..else
syntax in place of ternary operations, given that Crystal is meant to be read like English, and then
is only used in case..in
/case..when
statements anyway. But that's probably for another issue.
I believe that the way pseudo-methods are parsed should be changed to be more fault-tolerant for the sake of consistency with the language. The following example fails to compile with the error message
unexpected token: bar
:This fails to compile with the error message:
Error: unexpected token: "DELIMITER_START" (expected symbol)
And this compiles and runs fine despite being invalid syntax:
Pseudo-methods are explicitly treated differently by the lexer and parser, when really they should be methods with additional restrictions, almost like reserved methods (which isn't exactly a new concept in Crystal anyway).