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

88.88% Statements 96/108
80.43% Branches 37/46
100% Functions 2/2
88.88% Lines 96/108

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 1091x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5285x 5285x 5285x 5285x 5285x 5285x 5285x 3758x 3758x 3759x 3757x 3757x 2x 2x 3757x 5284x 5285x 3757x 3757x 3757x 1x 1x 1x 1x 3757x 5283x 5283x 5285x 5285x         5282x 5282x 5285x 5285x 3x 3x 3x 3x 5279x 5285x     5279x 5285x 1526x 18x 18x 1526x 1526x 1526x 5279x 5285x 204x         5285x 15x 15x 15x 15x 5285x 5285x 1x 1x 1x 1x 3545x 3545x 3545x 2x 3545x 3527x 3527x 16x 16x 3545x     16x 16x 3545x 11x 11x 5x 3545x 3545x 1x  
import * as Expressions from "../../2_statements/expressions";
import {StatementNode} from "../../nodes";
import {Source} from "../expressions/source";
import {Target} from "../expressions/target";
import {InlineData} from "../expressions/inline_data";
import {AbstractType} from "../../types/basic/_abstract_type";
import {StatementSyntax} from "../_statement_syntax";
import {TypeUtils} from "../_type_utils";
import {SyntaxInput, syntaxIssue} from "../_syntax_input";
import {Dereference} from "../expressions/dereference";
import {IdentifierMeta} from "../../types/_typed_identifier";
import {AnyType, DataType, PackedType} from "../../types/basic";
import {ExpressionNode, TokenNode} from "../../nodes";
 
export class Move implements StatementSyntax {
  public runSyntax(node: StatementNode, input: SyntaxInput): void {
    const targets = node.findDirectExpressions(Expressions.Target);
    const firstTarget = targets[0];
 
    const inline = firstTarget?.findDirectExpression(Expressions.InlineData);
 
    let targetType: AbstractType | undefined = undefined;
    if (inline === undefined) {
      targetType = firstTarget ? Target.runSyntax(firstTarget, input) : undefined;
      for (const t of targets) {
        if (t === firstTarget) {
          continue;
        }
        Target.runSyntax(t, input);
      }
    }
 
    if (inline === undefined && firstTarget !== undefined) {
// hmm, does this do the scoping correctly? handle constants etc? todo
      const found = input.scope.findVariable(firstTarget.concatTokens());
      if (found && found.getMeta().includes(IdentifierMeta.ReadOnly)) {
        const message = `"${firstTarget.concatTokens()}" cannot be modified, it is readonly`;
        input.issues.push(syntaxIssue(input, firstTarget.getFirstToken(), message));
        return;
      }
    }
 
    const source = node.findDirectExpression(Expressions.Source);
    let sourceType = source ? Source.runSyntax(source, input, targetType) : undefined;
    if (sourceType === undefined) {
      const message = "No source type determined";
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
      return;
    }
 
    if (inline === undefined
        && node.findDirectTokenByText("=") !== undefined
        && Move.isRefInferIntoGenericFieldSymbol(targets, source, targetType)) {
      const message = "REF #( ) cannot infer a type from a generic target, use REF data( ) or a named type";
      input.issues.push(syntaxIssue(input, source!.getFirstToken(), message));
      return;
    }
 
    if (node.findDirectExpression(Expressions.Dereference)) {
      sourceType = Dereference.runSyntax(node, sourceType, input);
    }
 
    if (inline) {
      if (sourceType instanceof PackedType && source?.findDirectExpression(Expressions.ArithOperator)) {
        sourceType = new PackedType(8, 0);
      }
      InlineData.runSyntax(inline, input, sourceType);
      targetType = sourceType;
    }
 
    if (node.findDirectTokenByText("?=")) {
      if (new TypeUtils(input.scope).isCastable(sourceType, targetType) === false) {
        const message = "Incompatible types";
        input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
        return;
      }
    } else if (new TypeUtils(input.scope).isAssignableNew(sourceType, targetType, source) === false) {
      const message = "Incompatible types";
      input.issues.push(syntaxIssue(input, node.getFirstToken(), message));
      return;
    }
 
  }
 
  /** target = REF #( ... ), with the single target being a field symbol typed "any" or "data",
   *  the type of REF # is inferred from the target, which is generic */
  private static isRefInferIntoGenericFieldSymbol(targets: readonly ExpressionNode[],
                                                  source: ExpressionNode | undefined,
                                                  targetType: AbstractType | undefined): boolean {
    if (targets.length !== 1 || source === undefined) {
      return false;
    } else if (!(targetType instanceof AnyType) && !(targetType instanceof DataType)) {
      return false;
    }
 
    const targetChildren = targets[0].getChildren();
    if (targetChildren.length !== 1 || !(targetChildren[0].get() instanceof Expressions.TargetFieldSymbol)) {
      return false;
    }
 
    const first = source.getFirstChild();
    if (!(first instanceof TokenNode) || first.getFirstToken().getStr().toUpperCase() !== "REF") {
      return false;
    }
 
    return source.findDirectExpression(Expressions.TypeNameOrInfer)?.concatTokens() === "#";
  }
}