Leomar de Souza
Leomar de Souza

Reputation: 757

UnexpectedType error to refactoring

I'm having a problem while trying to refactor an if/else statement in a switch statement. The grammar used is Java 8, according to the syntax below:

If else statement

syntax IfThenStatement = "if" "(" Expression ")" Statement ;

syntax IfThenElseStatement = "if" "(" Expression ")" StatementNoShortIf "else" Statement ;

syntax IfThenElseStatementNoShortIf = "if" "(" Expression ")" StatementNoShortIf "else" StatementNoShortIf ;

Switch statement

syntax SwitchStatement = "switch" "(" Expression ")" SwitchBlock ; 

syntax SwitchBlock = "{" SwitchBlockStatementGroup* SwitchLabel* "}" ;

syntax SwitchBlockStatementGroup = SwitchLabels BlockStatements ;

syntax SwitchLabels = SwitchLabel+ ; 

syntax SwitchLabel = "case" ConstantExpression ":" 
                   | "default" ":" 
                   ;

I'm using the following code to perform the refactoring:

CompilationUnit refactorIfElseStatement(CompilationUnit unit) =  visit(unit) {
    case (IfThenElseStatement) `if (<Identifier idIf>.equals(<StringLiteral stringCompare>)) <StatementNoShortIf stmtIf> else <Statement stmtElse>` => 
        (SwitchStatement) `switch (<Identifier idIf>) {<SwitchBlockStatementGroup switchBlock>}`
        when switchBlock := generateCaseFromIfElseStatement(stmtElse, idIf)
};

SwitchBlockStatementGroup generateCaseFromIfElseStatement(Statement stmt, Identifier idIf) = visit(stmt){
    case (StatementNoShortIf) `<StatementNoShortIf stmt1>` => 
         (SwitchBlockStatementGroup) `default: <BlockStatements stmt1>`

    case (IfThenElseStatement) `if (idIf.equals(<StringLiteral stringCompare>)) <StatementNoShortIf stmtIf> else <Statement stmtElse>` =>
         (SwitchBlockStatementGroup) `case <ConstantExpression stringCompare> : { <BlockStatements stmtIf> }`
};

However, when you run the refactoring code, the following error is displayed:

Expected SwitchBlockStatementGroup, but got Statement Advice: |http://tutor.rascal-mpl.org/Errors/Static/UnexpectedType/UnexpectedType.html|

At first, the refactoring is simple as can be seen in the code block below. Subsequently, I intend to increase its complexity to meet other cases.

if (string.equals("boo")){
    System.out.println("is a boo");
}
else if (string.equals("blah")){
    System.out.println("is a blah");
}
else if (string.equals("foo")){
    System.out.println("is a foo");
}
else{
    System.out.print("is a default");
}

Upvotes: 1

Views: 134

Answers (2)

Rodrigo Bonifacio
Rodrigo Bonifacio

Reputation: 262

I fixed the problem using (at the top level) a visitor + a recursive function based on a switch-case statement of Rascal (without using another visitor expression). Note that this code is still experimental, and I have only tested it using a few test cases.

CompilationUnit refactorToSwitchString(CompilationUnit unit) = top-down-break visit(unit) {
 case (Statement)`if(<Identifier id>.equals(<StringLiteral lit>)) {<Statement stmt1> } else <Statement stmt2>` => 
 (Statement)`switch(<Identifier id>) { case <StringLiteral lit> : { <Statement stmt1> }  <SwitchBlockStatementGroup* stmt3> }` 
 when stmt3 := buildSwitchGroups(stmt2, id)
};


SwitchBlockStatementGroups buildSwitchGroups(stmt, id) {
 switch(stmt) {
  case (Statement)`if(<Identifier id>.equals(<StringLiteral lit>)) { <Statement stmt1> } else <Statement stmt2>` : {
    stmt3 = buildSwitchGroups(stmt2, id) ;
    return (SwitchBlockStatementGroups)`case <StringLiteral lit> : { <Statement stmt1> break; } <SwitchBlockStatementGroup* stmt3>`;
  }
  case (Statement)`if(<Identifier id>.equals(<StringLiteral lit>)) <Statement stmt1>` : {
   return (SwitchBlockStatementGroups) `case <StringLiteral lit> : { <Statement stmt1> break;`;
  }
  case (Statement)`<Statement stmt>` : {
     return (SwitchBlockStatementGroups)`default : <Statement stmt>` ;
  }
};

I had to change the grammar a bit, introducing a definition like:

syntax SwitchBlock = "{" SwitchBlockStatementGroups SwitchLabel* "}" ;
syntax SwitchBlockStatementGroups = SwitchBlockStatementGroup* ;

Though I am not sure if this change was really necessary.

Upvotes: 2

Jurgen Vinju
Jurgen Vinju

Reputation: 6696

In Rascal a visit statement can only replace a non-terminal by the same non-terminal.

  • the reason is that in order for the rank-2 polymorphic semantics of the visit statement to type-check correctly, for all nodes it visits it can only replace the values of the type it finds there by another type which is a subtype of the matched type
  • in this example SwitchBlockStatementGroup is not a sub-type of StatementNoShortIf which triggers the error message, and the same for IfThenElseStatement and SwitchBlockStatementGroup
  • the specific error message is confusing
  • the code as you have written it now would probably not generate correct Java code if the type-checker would have ignored it.
  • if you use visit to transform a parse tree in Rascal, all intermediate results are guaranteed syntactically correct Java

So this means the code has to be fixed to jump through the type-safety and syntax-safety hoop. Options:

  1. change the grammar to have fewer non-terminals; this can be done, but it is not easy to do correctly. It will make your life easier down the road. Rascal grammar use fewer non-terminals (i.e. only one Statement and one Expression), often supported by explicit disambiguation constructs (like priorities > and follow restrictions !>>). Also, we avoid the use chain rules (injections) like syntax A = B to avoid having to remember additional types and to avoid writing overly specific patterns.
  2. zoom out to the Statement level and rewrite your pattern to replace one Statement by another Statement. In this case, this means matching the surrounding if-then-else on the left-hand side and rebuilding it on the right-hand side, and surrounding the SwitchBlockStatementGroup with the right brackets to make it a statement, etc.
  3. I sometimes write reusable functions to translate between different sub-non-terminals.
  4. The final workaround is to extend the grammar (in a separate module) with extensions to allow "incorrect" parse trees, and later transform them away or trust that they do coincide with correct Java. Examples I've seen are "BlockExpressions" where a block can be an expression temporarily during a source-to-source compiler only to be transformed away in a later stage to pure Java. I would not recommend this option in this case since the extended grammar is bound to be ambiguous.

Upvotes: 2

Related Questions