Skip to content

Cannot use unary method taking 2 operators #13282

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

Closed
WojciechMazur opened this issue Aug 11, 2021 · 14 comments · Fixed by #13328
Closed

Cannot use unary method taking 2 operators #13282

WojciechMazur opened this issue Aug 11, 2021 · 14 comments · Fixed by #13328

Comments

@WojciechMazur
Copy link
Contributor

Hey, I've probably found a bug inside parser. This code would work without problems in Scala 2. It's a blocker for allowing to compile Scala Native with Scala 3. If it's not a bug, but expected behaviour we would need to change our syntax for Scala 3 users.

Compiler version 3.0.1

Minimized code

class Ptr[T](var value: T):
   def `unary_!` : T = value
   def `unary_!_=`(value: T): Unit = this.value = value
end Ptr

println(!x)
!x = 10                                                                                          

Output

1 |!x = 10
  |   ^
  |   end of statement expected but '=' found

Expectation

Should not fail, since it works in Scala 2

@som-snytt
Copy link
Contributor

Does the Puzzler Guy have a Batsignal?

@ekrich
Copy link
Contributor

ekrich commented Aug 11, 2021

No but we have a Csignal 😄

@som-snytt
Copy link
Contributor

Maybe it's not an issue about the two expansions (of unary op and assignment) but that the backticked identifier is not kosher.

unary_!_= is not x_= where x is "letter followed by letters and digits".

There have been previous bad interactions with backticked identifiers, with case classes, so probably the spec should specify this problematic space. (The spec says not all backticked identifiers are supported, but doesn't add that not all of them will interoperate with other syntaxy features.)

@odersky
Copy link
Contributor

odersky commented Aug 13, 2021

According to https://scala-lang.org/files/archive/spec/2.13/13-syntax-summary.html this should not parse in 2.x either. The LHS of an assignment must be an identifier, or a field reference.

@odersky
Copy link
Contributor

odersky commented Aug 13, 2021

In fact I have no idea by what reasoning this code could be valid. Can some clarify this?

@WojciechMazur
Copy link
Contributor Author

In such case, it looks like Scala Native was using forbidden syntax for quite a long time! To make some better context here's actual usage in Scala 2 code and original definition of method

@sjrd
Copy link
Member

sjrd commented Aug 13, 2021

In fact I have no idea by what reasoning this code could be valid. Can some clarify this?

The reasoning is

  • !x is equivalent to x.unary_!
  • x.y = z is equivalent to x.y_=(z), so x.unary_! = z is equivalent to x.unary_!_=(z)
  • Both rewrites compose so that !x = z is equivalent to x.unary_!_=(z)

@som-snytt
Copy link
Contributor

It's worth saying that it's a very clever use of forbidden syntax. I also like that the operator was upgraded from boring := in a commit with title, "doc comments", and indeed it was mostly comments, plus this innovation.

When support for * as an identifier was improved, there was a joke about pointer dereference syntax. Perhaps we should have asked for unary_* as an experimental feature.

I'll take a swing at closing the syntax hole in Scala 2.

I may vacillate a bit on whether it should be allowed. But !(cstr + c) = bytes(c) makes me go, It looks like update syntax, but WAT.

This could open the door for interesting test framework syntax.

Never again will I say, "It's just syntax."

For unary ops, the specese is "is equivalent to", but for assignment, it is "is interpreted as".

@densh
Copy link

densh commented Aug 15, 2021

I'd like to highlight that having a straightforward syntax for pointers is extremely important for Scala Native's interop. It's not just a clever fringe feature that one uses occasionally, but literally the main way to work with pointers. !x and !x = v is really easy to learn for newcomers since it maps one-to-one to something that most people already know coming from other low-level languages (such as *x and *x = v in C), and bang is also used for references in ML-family languages.

@som-snytt
Copy link
Contributor

Those are some great pointers.

@nicolasstucki
Copy link
Contributor

Note that x.unary_! = 10 does get desugared to x.unary_!_=(10). We are probably only missing the parser rule for !x = y syntax.

@odersky
Copy link
Contributor

odersky commented Aug 18, 2021

We are probably only missing the parser rule

It's not the parser rule, it's the published syntax, which does not allow this case, and never did. But it seems nobody is looking at that. Which is a pity, really.

@som-snytt
Copy link
Contributor

I agree that if the decision is made to accept the syntax, the syntax summary must be updated.

You can't rely on "is equivalent to" to mean "is syntactically equivalent to".

I only looked at the syntax summary after the previous odersky comment, full disclosure.

@som-snytt
Copy link
Contributor

Sample interview question at https://contributors.scala-lang.org/t/functional-syntax/5062/38?u=som-snytt

def f = - -42

which spoiler alert doesn't parse anywhere. It's like the meme about precedence, "Most people can't get this math problem."

olsdavis pushed a commit to olsdavis/dotty that referenced this issue Apr 4, 2022
@Kordyjan Kordyjan added this to the 3.1.0 milestone Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants