|
16 | 16 | import spoon.reflect.declaration.CtParameter; |
17 | 17 | import spoon.reflect.declaration.CtType; |
18 | 18 | import liquidjava.utils.Utils; |
| 19 | +import liquidjava.utils.constants.Keys; |
| 20 | +import liquidjava.processor.context.CallResult; |
| 21 | +import liquidjava.processor.context.VariableInstance; |
| 22 | +import liquidjava.rj_language.ast.BinaryExpression; |
| 23 | +import liquidjava.rj_language.ast.Expression; |
| 24 | +import liquidjava.rj_language.ast.FunctionInvocation; |
| 25 | +import liquidjava.rj_language.ast.LiteralBoolean; |
| 26 | +import liquidjava.rj_language.ast.Var; |
| 27 | +import liquidjava.rj_language.ast.formatter.ExpressionFormatter; |
| 28 | +import spoon.reflect.cu.SourcePosition; |
| 29 | + |
| 30 | +import static liquidjava.rj_language.opt.VCSimplificationUtils.containsExpression; |
| 31 | +import static liquidjava.rj_language.opt.VCSimplificationUtils.factsOf; |
| 32 | +import static liquidjava.rj_language.opt.VCSimplificationUtils.isTrue; |
19 | 33 |
|
20 | 34 | /** |
21 | 35 | * The notes of a refinement error that say where the values its expected refinement is about come from. Each is given |
@@ -53,6 +67,64 @@ static List<String> unrefinedParameters(RefinementError error, CtElement checked |
53 | 67 | return notes; |
54 | 68 | } |
55 | 69 |
|
| 70 | + /** |
| 71 | + * For each argument of expected that is a call to a refined method, what the method's return refinement guarantees |
| 72 | + * about it. An equality defining a ghost, {@code _ == size(this)}, is left out when the ghost is nowhere else in |
| 73 | + * the refinement: it then constrains no value, so what is left is all it says about the value |
| 74 | + */ |
| 75 | + static List<String> returnedValues(RefinementError error, Context context) { |
| 76 | + List<String> notes = new ArrayList<>(); |
| 77 | + for (String variable : error.getExpected().getVariableNames().stream().distinct().toList()) { |
| 78 | + // a call argument, which has no variable of its own and is shown as the call |
| 79 | + if (!(context.getVariableByName(variable)instanceof VariableInstance instance) |
| 80 | + || instance.getReturnedBy().isEmpty() || context.getVariableFromInstance(instance) != null) |
| 81 | + continue; |
| 82 | + CallResult call = instance.getReturnedBy().get(); |
| 83 | + SourcePosition declared = call.function().getPlacementInCode() == null ? null |
| 84 | + : call.function().getPlacementInCode().getAnnotationPosition(); |
| 85 | + if (declared == null || !declared.isValidPosition() || declared.getFile() == null) |
| 86 | + continue; |
| 87 | + Expression guaranteed = withoutGhostDefinitions(call.returned().getExpression()); |
| 88 | + String value = ExpressionFormatter.format(new Var(variable), error.getDisplayNames()); |
| 89 | + String says = isTrue(guaranteed) ? "guarantees nothing about " + value |
| 90 | + : "guarantees only " + ExpressionFormatter.format( |
| 91 | + guaranteed.substitute(new Var(Keys.WILDCARD), new Var(variable)), error.getDisplayNames()); |
| 92 | + notes.add(String.format("From: %s (%s:%d) %s", value, declared.getFile().getName(), declared.getLine(), |
| 93 | + says)); |
| 94 | + } |
| 95 | + return notes; |
| 96 | + } |
| 97 | + |
| 98 | + private static Expression withoutGhostDefinitions(Expression refinement) { |
| 99 | + List<Expression> facts = factsOf(refinement); |
| 100 | + Expression kept = new LiteralBoolean(true); |
| 101 | + for (Expression fact : facts) { |
| 102 | + boolean definesGhost = ghostDefined(fact).filter( |
| 103 | + ghost -> facts.stream().noneMatch(other -> other != fact && containsExpression(other, ghost))) |
| 104 | + .isPresent(); |
| 105 | + if (!definesGhost) |
| 106 | + kept = isTrue(kept) ? fact : new BinaryExpression(kept, "&&", fact); |
| 107 | + } |
| 108 | + return kept; |
| 109 | + } |
| 110 | + |
| 111 | + /** The ghost application {@code g(...)} of a fact {@code _ == g(...)} */ |
| 112 | + private static Optional<Expression> ghostDefined(Expression fact) { |
| 113 | + if (!(fact instanceof BinaryExpression equality) || !"==".equals(equality.getOperator())) |
| 114 | + return Optional.empty(); |
| 115 | + Expression left = equality.getFirstOperand(); |
| 116 | + Expression right = equality.getSecondOperand(); |
| 117 | + if (isWildcard(left) && right instanceof FunctionInvocation) |
| 118 | + return Optional.of(right); |
| 119 | + if (isWildcard(right) && left instanceof FunctionInvocation) |
| 120 | + return Optional.of(left); |
| 121 | + return Optional.empty(); |
| 122 | + } |
| 123 | + |
| 124 | + private static boolean isWildcard(Expression expression) { |
| 125 | + return expression instanceof Var var && Keys.WILDCARD.equals(var.getName()); |
| 126 | + } |
| 127 | + |
56 | 128 | /** |
57 | 129 | * Whether the parameter is written with a refinement, even {@code true}, or a specification of its method refines |
58 | 130 | * it, such as an external one, which the method's body is not checked against |
|
0 commit comments