Skip to content
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

SIP-53 - Quote pattern explicit type variable syntax #17362

Merged
merged 3 commits into from
Jun 21, 2023

Conversation

nicolasstucki
Copy link
Contributor

@nicolasstucki nicolasstucki commented Apr 28, 2023

@smarter
Copy link
Member

smarter commented May 2, 2023

Could you add a test case related to semicolon inference? scala/improvement-proposals#59 (comment)

@nicolasstucki nicolasstucki force-pushed the sip-53 branch 2 times, most recently from 14cf508 to a726494 Compare May 2, 2023 15:55
@neko-kai

This comment was marked as off-topic.

@nicolasstucki
Copy link
Contributor Author

This is a binary-compatible change. Now that it is also experimental, it seems it no longer needs a minor release.

@smarter smarter removed the needs-minor-release This PR cannot be merged until the next minor release label May 11, 2023
@Kordyjan Kordyjan added the release-notes Should be mentioned in the release notes label May 12, 2023
compiler/src/dotty/tools/dotc/ast/Desugar.scala Outdated Show resolved Hide resolved
Comment on lines 382 to 385
val untpdTypeVariables = stats.takeWhile {
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
case _ => false
}.asInstanceOf[List[untpd.TypeDef]]
val otherStats = stats.dropWhile {
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
case _ => false
}
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this equivalent to:

Suggested change
val untpdTypeVariables = stats.takeWhile {
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
case _ => false
}.asInstanceOf[List[untpd.TypeDef]]
val otherStats = stats.dropWhile {
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
case _ => false
}
val (untpdTypeVariables, otherStats) = stats.partition:
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
case _ => false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of quoted type patterns, it might be equivalent. But this might not be the case with expression quote patterns. Consider this ill-formed pattern '{ type t; $x; type u; $y }, in this case we only want to desugar type t as a type variable. When checking the pattern tpye u will chase an error. Changing the way we partition would the way we emit errors in those ill-formed patterns.

Copy link
Member

Choose a reason for hiding this comment

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

in this case we only want to desugar type t as a type variable.

Right, this should be achievable by using .span instead of .partition.

Comment on lines +1757 to +1758
if in.token == SEMI then in.nextToken()
if in.isNewLine then in.nextToken()
Copy link
Member

Choose a reason for hiding this comment

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

Could this be:

Suggested change
if in.token == SEMI then in.nextToken()
if in.isNewLine then in.nextToken()
acceptStatSep()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That does not work.

docs/_docs/reference/metaprogramming/macros.md Outdated Show resolved Hide resolved
for untpdTypeVariable <- untpdTypeVariables do untpdTypeVariable.rhs match
case _: TypeBoundsTree => // ok
case LambdaTypeTree(_, body: TypeBoundsTree) => // ok
case _ => report.error("Quote type variable definition cannot be an alias", untpdTypeVariable.srcPos)
Copy link
Member

Choose a reason for hiding this comment

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

Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we declare a type variable with the same as an alias, then we know the type of that variable. It would be useless to solve the type of that type variable at runtime when pattern matching. This could also be seen as a missed optimization.

if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym$typeSymInfo; ... }`", tree.srcPos)
Copy link
Member

Choose a reason for hiding this comment

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

What's an example where this warning triggers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

import scala.quoted.*

type F[x <: Int, y <: Double]

def test(x: Type[?])(using Quotes) =
  x match
    case '[ F[t, t] ] => // warn // error
-- Warning: t/Test.scala:5:17 --------------------------------------------------
5 |    case '[ F[t, t] ] => // warn // error
  |                 ^
  |    Ignored bound <: Double
  |
  |    Consider defining bounds explicitly `'{ type t <: Int & Double; ... }`
-- [E057] Type Mismatch Error: t/Test.scala:5:15 -------------------------------
5 |    case '[ F[t, t] ] => // warn // error
  |               ^
  |               Type argument t does not conform to upper bound Double
  |
  | longer explanation available when compiling with `-explain`

The winning serves as extra information for the failure that will happen afterward. The warning complements the error message. We could make it an error, but I am not sure how to re-phrase the message in that case. This version seemed to be the simplest with the clearest hint.

I added a test case for this in tests/neg-macros/quote-type-variable-no-inference.scala.

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala Outdated Show resolved Hide resolved
*/
def quote(inPattern: Boolean): Tree =
atSpan(in.skipToken()) {
withinStaged(StageKind.Quoted | (if (inPattern) StageKind.QuotedPattern else 0)) {
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to be StageKind.QuotedPattern but not StageKind.Quoted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. It would be possible to make all StageKind disjoint if we want.

@@ -1761,6 +1758,20 @@ object Parsers {
if in.isNewLine then in.nextToken()
tdef

/** Quoted ::= ‘'’ ‘{’ Block ‘}’
* | ‘'’ ‘[’ Type ‘]’
Copy link
Member

Choose a reason for hiding this comment

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

Couldn't the grammar be refactored so that we can drop this case and only have Block and TypeBlock?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should just be TypeBlock. Type is already one of the cases of TypeBlock.

In syntax.md we have the correct one

Quoted            ::=  ‘'’ ‘{’ Block ‘}’
                    |  ‘'’ ‘[’ TypeBlock ‘]’

@nicolasstucki
Copy link
Contributor Author

Rebased

Support explicit type variable definition in quoted patterns.
This allows users to set explicit bounds or use the binding twice.
Previously this was only possible on quoted expression patterns case '{ ... }.

```scala
case '[type x; x] =>
case '[type x; Map[x, x]] =>
case '[type x <: List[Any]; x] =>
case '[type f[X]; f] =>
case '[type f <: AnyKind; f] =>
```

Fixes scala#10864
Fixes scala#11738
@nicolasstucki
Copy link
Contributor Author

Rebased

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-notes Should be mentioned in the release notes
Projects
None yet
4 participants