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 74x     74x 74x 74x 74x 74x 74x 7x 9x 9x 7x 74x 74x 15x 15x 15x 15x 74x 74x 39x 45x 45x 39x 74x 74x 74x 74x 74x 2x 2x 72x 72x 74x 2x 2x 2x 70x 70x 74x     70x 70x 70x 70x 74x 2x 2x 2x 2x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 4x 4x 64x 64x 64x 68x 68x 1x 1x 1x 132x 132x 132x 5x 5x 5x 5x 127x 127x 132x 58x 58x     58x 58x 58x 58x 58x 58x 19x 19x 19x 19x 58x 108x 108x 108x 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;
  }
}