|
5 | 5 | import java.util.stream.Collectors; |
6 | 6 |
|
7 | 7 | import liquidjava.diagnostics.DebugLog; |
| 8 | +import liquidjava.diagnostics.categories.ErrorCategory; |
8 | 9 | import liquidjava.diagnostics.errors.IllegalConstructorTransitionError; |
9 | 10 | import liquidjava.diagnostics.errors.InvalidRefinementError; |
10 | 11 | import liquidjava.diagnostics.errors.LJError; |
|
15 | 16 | import liquidjava.processor.refinement_checker.TypeChecker; |
16 | 17 | import liquidjava.processor.refinement_checker.TypeCheckingUtils; |
17 | 18 | import liquidjava.processor.state_machine.State; |
| 19 | +import liquidjava.processor.state_machine.StateErrorCategories; |
18 | 20 | import liquidjava.processor.state_machine.StateMachine; |
19 | 21 | import liquidjava.processor.state_machine.StateMachineBuilder; |
20 | 22 | import liquidjava.processor.state_machine.StateSolver; |
@@ -643,35 +645,47 @@ private static void changeState(TypeChecker tc, VariableInstance vi, RefinedFunc |
643 | 645 | SourcePosition declarationPosition = stateChanges.stream().filter(ObjectState::hasFrom) |
644 | 646 | .map(ObjectState::getFromPosition).filter(Objects::nonNull).findFirst() |
645 | 647 | .orElse(function.getPlacementInCode().getPosition()); |
646 | | - List<Hint> hints = stateHints(tc, function, instanceName, prevState, invocation); |
| 648 | + Optional<ReceiverStates> states = receiverStates(tc, function, instanceName, prevState, invocation); |
| 649 | + List<Hint> hints = states.map(r -> stateHints(r, function, invocation)).orElse(List.of()); |
| 650 | + ErrorCategory category = states.flatMap(r -> StateErrorCategories.categorize(r.machine(), r.current(), |
| 651 | + prevState, getGhostStatesFor(function.getTargetClass(), tc))).orElse(null); |
647 | 652 | tc.throwStateRefinementError(invocation.getPosition(), declarationPosition, prevState, |
648 | | - expectedStatesDisjunction, message, hints); |
| 653 | + expectedStatesDisjunction, message, hints, category); |
649 | 654 | } |
650 | 655 | } |
651 | 656 |
|
| 657 | + /** The state machine of the receiver's class and the states the receiver can be in before a call */ |
| 658 | + private record ReceiverStates(StateMachine machine, Set<State> current) { |
| 659 | + } |
| 660 | + |
652 | 661 | /** |
653 | | - * Hints on how to fix a call to {@code function} made in a state that does not allow it. They are best effort: if |
654 | | - * they cannot be computed, the error is reported without them |
| 662 | + * The receiver's states before a call to {@code function} that its state does not allow. Best effort: if they |
| 663 | + * cannot be computed, the error is reported without hints or category |
655 | 664 | */ |
656 | | - private static List<Hint> stateHints(TypeChecker tc, RefinedFunction function, String instanceName, Predicate state, |
657 | | - CtElement invocation) { |
658 | | - Optional<String> receiver = receiverOf(invocation); |
659 | | - if (receiver.isEmpty() || !StateMachineBuilder.canModel(function)) |
660 | | - return List.of(); |
| 665 | + private static Optional<ReceiverStates> receiverStates(TypeChecker tc, RefinedFunction function, |
| 666 | + String instanceName, Predicate state, CtElement invocation) { |
661 | 667 | try { |
662 | 668 | Optional<StateMachine> machine = tc.getStateMachine(function.getTargetClass(), invocation); |
663 | 669 | if (machine.isEmpty()) |
664 | | - return List.of(); |
| 670 | + return Optional.empty(); |
665 | 671 | Set<State> current = new StateSolver(tc, invocation.getPosition()).possibleStates(machine.get().states(), |
666 | 672 | state, instanceName); |
667 | | - return StateHints.forCall(machine.get(), function, current, receiver.get()); |
| 673 | + return Optional.of(new ReceiverStates(machine.get(), current)); |
668 | 674 | } catch (RuntimeException e) { |
669 | | - // a failure to compute hints must not replace the error being reported |
670 | | - DebugLog.warn("Could not compute state hints: " + e.getMessage()); |
671 | | - return List.of(); |
| 675 | + // a failure to analyse the states must not replace the error being reported |
| 676 | + DebugLog.warn("Could not compute the receiver's states: " + e.getMessage()); |
| 677 | + return Optional.empty(); |
672 | 678 | } |
673 | 679 | } |
674 | 680 |
|
| 681 | + /** Hints on how to fix a call to {@code function} made in a state that does not allow it */ |
| 682 | + private static List<Hint> stateHints(ReceiverStates states, RefinedFunction function, CtElement invocation) { |
| 683 | + Optional<String> receiver = receiverOf(invocation); |
| 684 | + if (receiver.isEmpty() || !StateMachineBuilder.canModel(function)) |
| 685 | + return List.of(); |
| 686 | + return StateHints.forCall(states.machine(), function, states.current(), receiver.get()); |
| 687 | + } |
| 688 | + |
675 | 689 | /** |
676 | 690 | * The receiver of {@code invocation} as written in the code, if it names an object that calls can be added to: a |
677 | 691 | * variable, a field or {@code this} |
|
0 commit comments