重构的 UnexpectedType 错误

UnexpectedType error to refactoring

我在尝试重构 switch statement 中的 if/else statement 时遇到问题。使用的语法是Java8,语法如下:

If else 语句

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

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

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

切换语句

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

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

syntax SwitchBlockStatementGroup = SwitchLabels BlockStatements ;

syntax SwitchLabels = SwitchLabel+ ; 

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

我正在使用以下代码执行重构:

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> }`
};

但是,当你运行重构代码时,会显示如下错误:

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

首先,重构很简单,如下面的代码块所示。随后,我打算增加它的复杂度来满足其他情况。

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");
}

在 Rascal 中,访问语句只能用相同的非终结符替换非终结符。

  • 原因是,为了使 visit 语句的 rank-2 多态语义能够正确进行类型检查,对于它访问的所有节点,它只能替换它在那里找到的类型的值另一种类型是匹配类型的子类型
  • 在此示例中,SwitchBlockStatementGroup 不是触发错误消息的 StatementNoShortIf 的子类型,IfThenElseStatementSwitchBlockStatementGroup[=39= 也是如此]
  • 具体错误信息令人困惑
  • 如果类型检查器忽略它,您现在编写的代码可能不会生成正确的 Java 代码。
  • 如果你在 Rascal 中使用 visit 来转换解析树,所有中间结果都保证语法正确Java

所以这意味着必须修复代码才能跳过类型安全和语法安全的圈套。选项:

  1. 更改语法以减少非终结符;这是可以做到的,但要做到正确并不容易。这将使您的生活更轻松。 Rascal 语法使用较少的非终结符(即只有一个 Statement 和一个 Expression),通常由显式消歧结构支持(如优先级 > 和遵循限制 !>>)。此外,我们避免使用像 syntax A = B 这样的链式规则(注入),以避免必须记住其他类型并避免编写过于具体的模式。
  2. 缩小到 Statement 级别并重写您的模式以将一个 Statement 替换为另一个 Statement。在这种情况下,这意味着匹配左侧周围的 if-then-else 并在右侧重建它,并用右括号将 SwitchBlockStatementGroup 包围起来使其成为一个语句,等等。
  3. 我有时会编写可重用的函数来在不同的子非终端之间进行转换。
  4. 最终的解决方法是使用扩展来扩展语法(在单独的模块中)以允许 "incorrect" 解析树,然后将它们转换掉或相信它们确实与正确的 Java 重合。我见过的示例是 "BlockExpressions",其中块可以是源到源编译器期间的临时表达式,只是在稍后阶段被转换为纯 Java。在这种情况下我不推荐这个选项,因为扩展语法必然会产生歧义。

我使用(在顶层)一个访问者 + 一个基于 Rascal 的 switch-case 语句的递归函数解决了这个问题(没有使用另一个访问者表达式)。请注意,此代码仍处于试验阶段,我只使用了几个测试用例对其进行了测试。

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>` ;
  }
};

我不得不稍微更改语法,引入如下定义:

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

虽然我不确定是否真的有必要进行此更改。