All files / src/abap/5_syntax/statements perform.ts

95.04% Statements 115/121
85.71% Branches 24/28
100% Functions 2/2
95.04% Lines 115/121

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 1211x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 73x     73x 73x 73x 73x 73x 73x 7x 9x 9x 7x 73x 73x 15x 15x 15x 15x 73x 73x 38x 44x 44x 38x 73x 73x 73x 73x 73x 2x 2x 71x 71x 73x 2x 2x 2x 69x 69x 73x     69x 69x 69x 69x 73x 2x 2x 2x 2x 67x 67x 67x 67x 67x 67x 67x 67x 67x 67x 67x 67x 4x 4x 63x 63x 63x 67x 67x 1x 1x 1x 130x 130x 130x 5x 5x 5x 5x 125x 125x 130x 57x 57x     57x 57x 57x 57x 57x 57x 19x 19x 19x 19x 57x 106x 106x 106x 1x
import * as Expressions from "../../2_statements/expressions";
import * as Statements from "../../2_statements/statements";
import {ExpressionNode, StatementNode} from "../../nodes";
import {ReferenceType} from "../_reference";
import {Source} from "../expressions/source";
import {StatementSyntax} from "../_statement_syntax";
import {Target} from "../expressions/target";
import {SyntaxInput, syntaxIssue} from "../_syntax_input";
import {AssertError} from "../assert_error";
import {Dynamic} from "../expressions/dynamic";
import {AbstractType} from "../../types/basic/_abstract_type";
import {TypeUtils} from "../_type_utils";
import {IdentifierMeta, TypedIdentifier} from "../../types/_typed_identifier";
 
type Parameter = {node: ExpressionNode, type: AbstractType | undefined};
 
export class Perform implements StatementSyntax {
  public runSyntax(node: StatementNode, input: SyntaxInput): void {
    if (!(node.get() instanceof Statements.Perform)) {
      throw new AssertError("checkPerform unexpected node type");
    }
 
    ////////////////////////////
    // check parameters are defined
 
    const changing: Parameter[] = [];
    for (const c of node.findDirectExpressions(Expressions.PerformChanging)) {
      for (const s of c.findDirectExpressions(Expressions.Target)) {
        changing.push({node: s, type: Target.runSyntax(s, input)});
      }
    }
    const tables: Parameter[] = [];
    for (const t of node.findDirectExpressions(Expressions.PerformTables)) {
      for (const s of t.findDirectExpressions(Expressions.Source)) {
        tables.push({node: s, type: Source.runSyntax(s, input)});
      }
    }
    const using: Parameter[] = [];
    for (const u of node.findDirectExpressions(Expressions.PerformUsing)) {
      for (const s of u.findDirectExpressions(Expressions.SimpleSource3)) {
        using.push({node: s, type: Source.runSyntax(s, input)});
      }
    }
 
    ////////////////////////////
    // find FORM definition
 
    if (node.findFirstExpression(Expressions.IncludeName)) {
      return; // in external program, not checked, todo
    }
 
    const dynamic = node.findFirstExpression(Expressions.Dynamic);
    if (dynamic) {
      Dynamic.runSyntax(dynamic, input);
      return; // todo, maybe some parts can be checked
    }
 
    const expr = node.findFirstExpression(Expressions.FormName);
    if (expr === undefined) {
      return; // it might be a dynamic call
    }
 
    const name = expr.concatTokens();
 
    const found = input.scope.findFormDefinition(name);
    if (found === undefined) {
      const message = "FORM definition \"" + name + "\" not found";
      input.issues.push(syntaxIssue(input, expr.getFirstToken(), message));
      return;
    }
 
    input.scope.addReference(expr.getFirstToken(), found, ReferenceType.FormReference, input.filename);
 
    ////////////////////////////
    // check parameters match
 
    // deferred, the FORM might be defined after the PERFORM, its parameters are only typed
    // once the FORM statement itself has been traversed
    input.deferred.push(() => {
      // look up again, the definition is replaced when the FORM statement is traversed
      const def = input.scope.findFormDefinition(name) ?? found;
      if (this.checkParameters(node, tables, def.getTablesParameters(), "TABLES", input) === false) {
        return;
      }
      // USING and CHANGING are interchangeable, the actual parameters form a single positional list
      const formal = def.getUsingParameters().concat(def.getChangingParameters());
      this.checkParameters(node, using.concat(changing), formal, "USING/CHANGING", input);
    });
  }
 
  // returns false if an issue was reported
  private checkParameters(node: StatementNode, actual: Parameter[], formal: readonly TypedIdentifier[],
                          kind: string, input: SyntaxInput): boolean {
 
    if (actual.length !== formal.length) {
      const message = `PERFORM, expected ${formal.length} ${kind} parameters, found ${actual.length}`;
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
      return false;
    }
 
    const typeUtils = new TypeUtils(input.scope);
    for (let i = 0; i < actual.length; i++) {
      const {node: source, type} = actual[i];
      if (type === undefined) {
        continue;
      }
      const parameter = formal[i];
      const structureTyping = parameter.getMeta().includes(IdentifierMeta.FormParameterStructure);
      const assignable = structureTyping
        ? typeUtils.isAssignableStructureTyping(type, parameter.getType(), source)
        : typeUtils.isAssignableStrict(type, parameter.getType(), source);
      if (assignable === false) {
        const message = `PERFORM parameter type not compatible, ${parameter.getName()}`;
        input.issues.push(syntaxIssue(input, source.getFirstToken(), message));
        return false;
      }
    }
 
    return true;
  }
}