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

Type ascriptions not accepted in tail position #7841

Closed
radeusgd opened this issue Sep 19, 2023 · 13 comments · Fixed by #11289
Closed

Type ascriptions not accepted in tail position #7841

radeusgd opened this issue Sep 19, 2023 · 13 comments · Fixed by #11289
Assignees
Labels
--bug Type: bug -parser p-lowest Should be completed at some point

Comments

@radeusgd
Copy link
Member

The PR #7796 gave us the ability to check / trigger conversions of types by a type ascription (a : Xyz).

Apparently, this is not accepted by the parser in tail position.

from Standard.Base import all

f1 v = v.each x->
    y = x : Text
    y

f2 v = v.each x->
    x : Text

main =
    IO.println (f1 ["foo"])
    IO.println (f2 ["foo"])

yields

C:\NBO\repr\type-check-parse.enso:7:15: warning: Unused function argument x.
    7 | f2 v = v.each x->
      |               ^
C:\NBO\repr\type-check-parse.enso:8:5: error: Unexpected type signature.
    8 |     x : Text
      |     ^~~~~~~~
Aborting due to 1 errors and 1 warnings.
Execution finished with an error: Compilation aborted due to errors.

So f1 is happily accepted but f2 is not.

The repro itself shows how to work-around this - we just need to add an intermediate variable. Thus it's rather low priority, but still - it seems there is no reason to disallow a check in there.

@radeusgd radeusgd added p-lowest Should be completed at some point --bug Type: bug -compiler -parser labels Sep 19, 2023
@kazcw kazcw self-assigned this Sep 19, 2023
@kazcw kazcw removed the -compiler label Sep 19, 2023
@kazcw
Copy link
Contributor

kazcw commented Sep 19, 2023

Currently the parser allows type signatures anywhere in a block. We could treat the last line of a block specially, since a type ascription is clearly more likely to be the intent in that case. In the meantime another option for a workaround would be (x : Text), which is a type ascription because type signatures are not allowed inside expressions.

@radeusgd
Copy link
Member Author

radeusgd commented Sep 19, 2023

In the meantime another option for a workaround would be (x : Text), which is a type ascription because type signatures are not allowed inside expressions.

Actually, I checked (x : Text) and it does not work either:

from Standard.Base import all

f1 v = v.each x->
    y = x : Text
    y

f2 v = v.each x->
    (x : Text)

main =
    IO.println (f1 ["foo"])
    IO.println (f2 ["foo"])

still yields

C:\NBO\repr\type-check-parse.enso:7:15: warning: Unused function argument x.
    7 | f2 v = v.each x->
      |               ^
C:\NBO\repr\type-check-parse.enso:8:6: error: Unexpected type signature.
    8 |     (x : Text)
      |      ^~~~~~~~
Aborting due to 1 errors and 1 warnings.
Execution finished with an error: Compilation aborted due to errors.

@kazcw
Copy link
Contributor

kazcw commented Sep 19, 2023

Huh. That is actually a separate bug, in TreeToIr; in the AST, that's a TypeAnnotated so it should not be treated as a type signature:

$ echo -n 'foo = x->    (y : Text)' | target/rust/release/enso-parser-debug
(BodyBlock #((Assignment (Ident foo) "=" (OprApp (Ident x) (Ok "->") (Group (TypeAnnotated (Ident y) ":" (Ident Text)))))))

@JaroslavTulach JaroslavTulach moved this from ❓New to ⚙️ Design in Issues Board Oct 7, 2024
@JaroslavTulach
Copy link
Member

JaroslavTulach commented Oct 7, 2024

Investigating. Following diff seems to enable the proper behavior:

diff --git engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala
index a524f70ebf..3270abef23 100644
--- engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala
+++ engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala
@@ -12,7 +12,8 @@ import org.enso.compiler.core.ir.{
   Function,
   Module,
   Name,
-  Pattern
+  Pattern,
+  Type
 }
 import org.enso.compiler.core.ir.module.scope.Definition
 import org.enso.compiler.core.ir.module.scope.definition.Method
@@ -163,6 +164,9 @@ case object FramePointerAnalysis extends IRPass {
         caseExpr.branches.foreach { branch =>
           processCaseBranch(branch)
         }
+      case asc: Type.Ascription =>
+        processExpression(asc.typed, graph)
+        processExpression(asc.signature, graph)
       case _ => ()
     }
     if (updateSymbols) {
diff --git engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala
index 10f52ce9d5..fc01693c85 100644
--- engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala
+++ engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala
@@ -315,7 +315,13 @@ case object TypeSignatures extends IRPass {
         lastSignature = None
         res
       case a => Some(resolveExpression(a))
-    } ::: lastSignature.map(errors.Unexpected.TypeSignature(_)).toList
+    } ::: lastSignature.map({
+      case asc @ Type.Ascription(_:Name, sig, comment, _, _) =>
+        asc.updateMetadata(
+          new MetadataPair(this, Signature(sig, comment))
+        )
+      case any => errors.Unexpected.TypeSignature(any)
+    }).toList
 
     block.copy(
       expressions = newExpressions.init,
diff --git engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala
index c7b47140b4..5c7a306bb6 100644
--- engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala
+++ engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala
@@ -878,7 +878,8 @@ class IrToTruffle(
       // Type contexts aren't currently really used. But we should still check the base type.
       extractAscribedType(comment, typeInContext.typed)
     case t => {
-      t.getMetadata(TypeNames) match {
+      val res = t.getMetadata(TypeNames)
+      res match {
         case Some(
               BindingsMap
                 .Resolution(binding @ BindingsMap.ResolvedType(_, _))
@@ -1287,7 +1288,24 @@ class IrToTruffle(
         case binding: Expression.Binding => processBinding(binding)
         case caseExpr: Case =>
           processCase(caseExpr, subjectToInstrumentation)
-        case typ: Tpe => processType(typ)
+        case asc : Tpe.Ascription =>
+            val checkNode =
+              extractAscribedType(asc.comment.orNull, asc.signature)
+            if (checkNode != null) {
+              val body = run(asc.typed, binding, subjectToInstrumentation)
+              ReadArgumentCheckNode.wrap(body, checkNode)
+            } else {
+              processType(asc)
+            }
+        case typ: Tpe =>
+            val checkNode =
+              extractAscribedType("ascribed", typ)
+            if (checkNode != null) {
+              //val body = run(typ.typed(), binding, subjectToInstrumentation)
+              ReadArgumentCheckNode.wrap(null, checkNode)
+            } else {
+              processType(typ)
+            }
         case _: Empty =>
           processEmpty()
         case _: Comment =>

@kazcw
Copy link
Contributor

kazcw commented Oct 7, 2024

In the Tree we have a distinction between two different expression : Type syntaxes:

  • TypeSignature, which declares the type of expression and is used in statement context (e.g. in the older out-of-band-types method definition syntax)
  • TypeAnnotated, which evaluates to expression and is used in a value context

I think this is an important syntactic distinction because it corresponds to a semantic distinction in whether the form causes expression to be evaluated. So I'm wondering, why not reflect this distinction in the IR representation?

@JaroslavTulach
Copy link
Member

* `TypeSignature`, which _declares_ the type of `expression` and
* `TypeAnnotated`, which _evaluates to_ `expression` and is used in a value context

I think this is an important syntactic distinction because it corresponds to a semantic distinction ... So I'm wondering, why not reflect this distinction in the IR representation?

I was surprised the x: Text is processed by TypesSignatures's lastSignature, because it is not signature, but an expression. The diff addresses that directly in TypesSignatures, but I'll try to avoid this confusion in TreeToIr.

@kazcw
Copy link
Contributor

kazcw commented Oct 8, 2024

* `TypeSignature`, which _declares_ the type of `expression` and
* `TypeAnnotated`, which _evaluates to_ `expression` and is used in a value context

I think this is an important syntactic distinction because it corresponds to a semantic distinction ... So I'm wondering, why not reflect this distinction in the IR representation?

I was surprised the x: Text is processed by TypesSignatures's lastSignature, because it is not signature, but an expression.

Yes, the problem is that IR doesn't distinguish type declaration statements (Tree.TypeSignature) from type-annotated expressions (Tree.TypeAnnotated)--in TreeToIr they are currently both translated to the same type ir.Type.Ascription.

@enso-bot
Copy link

enso-bot bot commented Oct 9, 2024

Jaroslav Tulach reports a new STANDUP for yesterday (2024-10-08):

Progress: .

@JaroslavTulach
Copy link
Member

JaroslavTulach commented Oct 9, 2024

There are signatures like this is_wanted : Node -> Boolean and then the signatures like

                plusChecked a b =
                    x = a + b
                    x:Integer

so far I have problems to differentiate them. This second case is still TypeSignature[" ", "x:Integer", [], Ident["", "x", [], Ident["", "x", 1, 1, false, 0, false, false]], TypeAnnotationOperator["", ":", 1, 1], Ident["", "Integer", [], Ident["", "Integer", 7, 7, false, 0, true, false]]]. I cannot differentiate from the case:

               plusChecked a b =
                    x:Integer
                    x = a + b
                    x

in this example, it is again TypeSignature[" ", "x:Integer", [], Ident["", "x", [], Ident["", "x", 1, 1, false, 0, false, false]], TypeAnnotationOperator["", ":", 1, 1], Ident["", "Integer", [], Ident["", "Integer", 7, 7, false, 0, true, false]]].

Those are both currently parsed as declarations, which I think we should change (in the parser):

If you want, please do so. I find way easier to just process the lastSignature as I did in this patch.

@kazcw
Copy link
Contributor

kazcw commented Oct 9, 2024

Those are both currently parsed as declarations, which I think we should change (in the parser):

Currently the parser allows type signatures anywhere in a block. We could treat the last line of a block specially, since a type ascription is clearly more likely to be the intent in that case. In the meantime another option for a workaround would be (x : Text), which is a type ascription because type signatures are not allowed inside expressions.

@enso-bot
Copy link

enso-bot bot commented Oct 10, 2024

Jaroslav Tulach reports a new STANDUP for yesterday (2024-10-09):

Progress: .

GitHub
Enso is dynamically compiled language built on top of JVM - e.g. GraalVM - and its Truffle framework as such programs written in Enso can run pretty fast - once the dynamic compiler does its job. T...
Discord
Discord is great for playing games and chilling with friends, or even building a worldwide community. Customize your own space to talk, play, and hang out.

@JaroslavTulach JaroslavTulach moved this from ⚙️ Design to 👁️ Code review in Issues Board Oct 10, 2024
@kazcw
Copy link
Contributor

kazcw commented Oct 10, 2024

There signatures like this is_wanted : Node -> Boolean and then the signatures like

                plusChecked a b =
                    x = a + b
                    x:Integer

so far I have problems to differentiate them. This second case is still TypeSignature[" ", "x:Integer", [], Ident["", "x", [], Ident["", "x", 1, 1, false, 0, false, false]], TypeAnnotationOperator["", ":", 1, 1], Ident["", "Integer", [], Ident["", "Integer", 7, 7, false, 0, true, false]]]. I cannot differentiate from the case:

               plusChecked a b =
                    x:Integer
                    x = a + b
                    x

in this example, it is again TypeSignature[" ", "x:Integer", [], Ident["", "x", [], Ident["", "x", 1, 1, false, 0, false, false]], TypeAnnotationOperator["", ":", 1, 1], Ident["", "Integer", [], Ident["", "Integer", 7, 7, false, 0, true, false]]].

Those are both currently parsed as declarations, which I think we should change (in the parser):

If you want, please do so. I find way easier to just process the lastSignature as I did in this patch.

With the patch, the code runs correctly, so the patch solves the problem from a compiler perspective. From a broader tooling perspective, if the AST is wrong, and the compiler output is "right", that shows we have two bugs. #11289 fixes the immediate issue; I've opened separate issues for fixing the parser and then the compiler:

@enso-bot
Copy link

enso-bot bot commented Oct 11, 2024

Jaroslav Tulach reports a new STANDUP for yesterday (2024-10-10):

Progress: .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
--bug Type: bug -parser p-lowest Should be completed at some point
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

3 participants