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

Throw Statements cannot be used as expressions #432

Open
Clashsoft opened this issue Oct 18, 2018 · 7 comments
Open

Throw Statements cannot be used as expressions #432

Clashsoft opened this issue Oct 18, 2018 · 7 comments
Assignees
Labels
bug:Compiler Error Bugs where the compiler encounters an internal error (exception) status:Scheduled Scheduled for the next release type:Bug Bugs and other unwanted behaviour
Projects

Comments

@Clashsoft
Copy link
Member

The following REPL input causes a compiler error:

> throw new RuntimeException
dyvilx.tools.compiler.backend.exception.StackUnderflowException
	at dyvilx.tools.compiler.backend.method.Frame.pop(Frame.java:250)
	at dyvilx.tools.compiler.backend.method.Frame.visitFieldInsn(Frame.java:745)
	at dyvilx.tools.compiler.backend.method.MethodWriterImpl.visitFieldInsn(MethodWriterImpl.java:796)
	at dyvilx.tools.compiler.backend.method.MethodWriter.visitFieldInsn(MethodWriter.java:141)
	at dyvilx.tools.compiler.ast.field.Field.writeStaticInit(Field.java:607)
	at dyvilx.tools.repl.context.REPLVariable.writeStaticInit(REPLVariable.java:132)
	at dyvilx.tools.compiler.ast.classes.ClassBody.writeStaticInit(ClassBody.java:990)
	at dyvilx.tools.compiler.ast.classes.CodeClass.writeStaticInit(CodeClass.java:636)
	at dyvilx.tools.compiler.ast.classes.CodeClass.write(CodeClass.java:564)
	at dyvilx.tools.compiler.backend.classes.ClassWriter.compile(ClassWriter.java:32)
	at dyvilx.tools.repl.context.REPLClassLoader.findClass(REPLClassLoader.java:57)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at dyvilx.tools.repl.context.REPLClassLoader.initialize(REPLClassLoader.java:84)
	at dyvilx.tools.repl.context.REPLContext.compileAndLoad(REPLContext.java:163)
	at dyvilx.tools.repl.context.REPLContext.endEvaluation(REPLContext.java:137)
	at dyvilx.tools.repl.DyvilREPL.evaluate(DyvilREPL.java:225)
	at dyvilx.tools.repl.DyvilREPL.processInput(DyvilREPL.java:205)
	at dyvilx.tools.repl.Main.readAndProcess(Main.java:55)
	at dyvilx.tools.repl.Main.main(Main.java:20)

The reason is that the REPL tries to assign the expression to a field, but the expression does not return.

@Clashsoft Clashsoft added this to the v0.42.0 milestone Oct 18, 2018
@Clashsoft Clashsoft self-assigned this Oct 18, 2018
@Clashsoft Clashsoft added bug:Compiler Error Bugs where the compiler encounters an internal error (exception) type:Bug Bugs and other unwanted behaviour status:Scheduled Scheduled for the next release labels Oct 18, 2018
@Clashsoft Clashsoft modified the milestones: v0.42.0, v0.41.1 Oct 20, 2018
@Clashsoft Clashsoft added status:Review status:Scheduled Scheduled for the next release and removed status:Scheduled Scheduled for the next release status:Review labels Nov 12, 2018
@Clashsoft Clashsoft added this to Scheduled in Dyvil Apr 7, 2019
@Clashsoft
Copy link
Member Author

Clashsoft commented May 9, 2020

This is a general problem with throw "expressions":

let x = throw new RuntimeException
print(throw new RuntimeException)

@Clashsoft Clashsoft added this to the v0.47.0 milestone May 9, 2020
@Clashsoft Clashsoft changed the title Throw Statement in REPL causes compiler error Throw Statements can be used as expressions May 9, 2020
@nikeee
Copy link

nikeee commented May 9, 2020

TypeScript (and probably other languages) solve this by introducing a never type.

let x = throw new RuntimeException
print(throw new RuntimeException) // error, because never is not assignable to something that is printable

They also use it for dead-code detection (this is not directly tied to the exception stuff, but plays well with it. It also requires control-flow based type checking):

const x = "foo"
if (x === "foo") {
    //....
} else {
    // x is of type never
}

Using this never construct, you can also assert that the cases of a switch statement are exhaustive during type checking:

function assertNever(value: never) {}
//...
const x = "foo";
switch(x)
{
    case "foo":
        return "bar";
    default: assertNever(x); // if there is a code path that would hit the default branch, x would not be never but something diffferent instead. This errors at compile time
}

C# does not have this but allows throws in expressions. This is useful in places like:

void Test(string arg) {
    this.Arg = arg ?? throw new ArgumentNullException();
}
string GetLastName()
{
    var parts = Name.Split(' ');
    return parts.Length > 1 ? parts[1] : throw new InvalidOperationException("Invalid name!");
}

It also helps when using lambdas in some cases:

void Test2() {
    SomeFunctionWithCallback(() => throw new NotImplementedException());

    var foo = "bar";
    var a = foo switch { // Switch expressions
        "bar" => "baz",
        _ => throw new NotSupportedException()
    }
}

Maybe this inspires you. The way Java does it (making throw a statement only) is not the only way of handling this case. Java recently tries to work around this decision by selectively allowing "throw" statements in the new pattern matching proposal. This wouldn't have been necessary if they did it different in the first place.

@Clashsoft
Copy link
Member Author

Hm, actually I already have a type like never (although it's called none). It is a subtype of every type and also the type that is inferred when using a throw "statement" as an expression. Now that I think about it forbidding throw from being used as an expression is a bad idea. Using it as part of a ternary or null coalescing (??) is a great application.

@Clashsoft Clashsoft changed the title Throw Statements can be used as expressions Throw Statements cannot be used as expressions May 9, 2020
@nikeee
Copy link

nikeee commented May 9, 2020

never (although it's called none)

I'm curious: Whats the difference to something like void?
Edit:
Looking at the language reference:
https://reference.dyvil.org/language-reference/types/basic-types

It seems that it is exactly the same as never (but without trivial infinite loop detection). It confused me because some languages use "none" when they mean something like "void".

@Clashsoft
Copy link
Member Author

Well the type system treats it differently because it is a bottom type. Also, it makes the difference between „function f():void returns nothing“ and „function g():none never returns at all“. Indeed the naming is confusing, I should rename it to never. I got the name from Scala‘s Nothing which has the same problem, except the language uses Unit instead of void. So at least they are consistent.

@nikeee
Copy link

nikeee commented May 10, 2020

„function f():void returns nothing“ and „function g():none never returns at all“

I think my phrasing was ambiguous (corrected now). It actually is the same as never in TS, but without trivial infinite loop detection (and unreachable code detection in general?).

Other languages use "none" as a unit type. I first had the impression that it was the same here. Thanks for clarification.

@Clashsoft
Copy link
Member Author

Yes, there is no unreachable code detection at all in the language. The compiler is not sophisticated enough for that and retrofitting it is a pain.

@Clashsoft Clashsoft removed this from the v0.47.0 milestone May 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug:Compiler Error Bugs where the compiler encounters an internal error (exception) status:Scheduled Scheduled for the next release type:Bug Bugs and other unwanted behaviour
Projects
Dyvil
  
Scheduled
Development

No branches or pull requests

2 participants