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 83x     83x 83x 83x 83x 83x 83x 7x 9x 9x 7x 83x 83x 15x 15x 15x 15x 83x 83x 46x 52x 52x 46x 83x 83x 83x 83x 83x 2x 2x 81x 81x 83x 2x 2x 2x 79x 79x 83x     79x 79x 79x 79x 83x 2x 2x 2x 2x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 77x 4x 4x 73x 73x 73x 77x 77x 1x 1x 1x 150x 150x 150x 5x 5x 5x 5x 145x 145x 150x 65x 65x     65x 65x 65x 65x 65x 65x 19x 19x 19x 19x 65x 126x 126x 126x 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;
  }
}