Skip to content

Commit 53e98af

Browse files
Local Variable Type Hints (SkriptLang#7892)
1 parent 07a0b34 commit 53e98af

36 files changed

Lines changed: 1203 additions & 116 deletions

src/main/java/ch/njol/skript/ScriptLoader.java

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ch.njol.skript.config.SectionNode;
66
import ch.njol.skript.config.SimpleNode;
77
import ch.njol.skript.events.bukkit.PreScriptLoadEvent;
8+
import ch.njol.skript.lang.ExecutionIntent;
89
import ch.njol.skript.lang.Section;
910
import ch.njol.skript.lang.SkriptParser;
1011
import ch.njol.skript.lang.Statement;
@@ -20,7 +21,7 @@
2021
import ch.njol.skript.util.SkriptColor;
2122
import ch.njol.skript.util.Task;
2223
import ch.njol.skript.util.Timespan;
23-
import ch.njol.skript.variables.TypeHints;
24+
import ch.njol.skript.variables.HintManager;
2425
import ch.njol.util.NonNullPair;
2526
import ch.njol.util.OpenCloseable;
2627
import ch.njol.util.StringUtils;
@@ -951,6 +952,14 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) {
951952

952953
ArrayList<TriggerItem> items = new ArrayList<>();
953954

955+
// Begin local variable type hints
956+
parser.getHintManager().enterScope(true);
957+
// Track if the scope has been frozen
958+
// This is the case when a statement that stops further execution is encountered
959+
// Further statements would not run, meaning the hints from them are inaccurate
960+
// When true, before exiting the scope, its hints are cleared to avoid passing them up
961+
boolean freezeScope = false;
962+
954963
boolean executionStops = false;
955964
for (Node subNode : node) {
956965
parser.setNode(subNode);
@@ -983,11 +992,18 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) {
983992

984993
items.add(item);
985994
} else if (subNode instanceof SectionNode subSection) {
986-
TypeHints.enterScope(); // Begin conditional type hints
987995

988996
RetainingLogHandler handler = SkriptLogger.startRetainingLog();
989997
find_section:
990998
try {
999+
// enter capturing scope
1000+
// it is possible that the line may successfully parse and initialize (via init), but some other issue
1001+
// prevents it from being able to load. for example:
1002+
// - a statement with a section that has no expression to claim the section
1003+
// - a statement with a section that has multiple expressions attempting to claim the section
1004+
// thus, hints may be added, but we do not want to save them as the line is invalid
1005+
parser.getHintManager().enterScope(false);
1006+
9911007
item = Section.parse(expr, "Can't understand this section: " + expr, subSection, items);
9921008
if (item != null)
9931009
break find_section;
@@ -1014,6 +1030,13 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) {
10141030
}
10151031
continue;
10161032
} finally {
1033+
// exit hint scope (see above)
1034+
HintManager hintManager = parser.getHintManager();
1035+
if (item == null) { // unsuccessful, wipe out hints
1036+
hintManager.clearScope(0, false);
1037+
}
1038+
hintManager.exitScope();
1039+
10171040
RetainingLogHandler afterParse = handler.backup();
10181041
handler.clear();
10191042
handler.printLog();
@@ -1023,9 +1046,6 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) {
10231046
}
10241047

10251048
items.add(item);
1026-
1027-
// Destroy these conditional type hints
1028-
TypeHints.exitScope();
10291049
} else {
10301050
continue;
10311051
}
@@ -1037,7 +1057,24 @@ public static ArrayList<TriggerItem> loadItems(SectionNode node) {
10371057
Skript.warning("Unreachable code. The previous statement stops further execution.");
10381058
}
10391059
executionStops = item.executionIntent() != null;
1060+
1061+
if (executionStops && !freezeScope) {
1062+
freezeScope = true;
1063+
// Execution might stop for some sections but not all
1064+
// We want to pass hints up to the scope that execution resumes in
1065+
if (item.executionIntent() instanceof ExecutionIntent.StopSections intent) {
1066+
parser.getHintManager().mergeScope(0, intent.levels(), true);
1067+
}
1068+
}
1069+
}
1070+
1071+
// If the scope was frozen, then we need to clear it to prevent passing up inaccurate hints
1072+
// They will have already been copied as necessary
1073+
if (freezeScope) {
1074+
parser.getHintManager().clearScope(0, false);
10401075
}
1076+
// Destroy local variable type hints for this section
1077+
parser.getHintManager().exitScope();
10411078

10421079
for (int i = 0; i < items.size() - 1; i++)
10431080
items.get(i).setNext(items.get(i + 1));

src/main/java/ch/njol/skript/Skript.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1264,7 +1264,6 @@ public void onDisable() {
12641264
if (disabled)
12651265
return;
12661266
disabled = true;
1267-
this.experimentRegistry = null;
12681267

12691268
if (!partDisabled) {
12701269
beforeDisable();
@@ -1279,6 +1278,8 @@ public void onDisable() {
12791278
Skript.exception(e, "An error occurred while shutting down.", "This might or might not cause any issues.");
12801279
}
12811280
}
1281+
1282+
this.experimentRegistry = null;
12821283
}
12831284

12841285
// ================ CONSTANTS, OPTIONS & OTHER ================

src/main/java/ch/njol/skript/command/Argument.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ public void set(final ScriptCommandEvent e, final Object[] o) {
130130
public T[] getCurrent(final Event e) {
131131
return current.get(e);
132132
}
133+
134+
public @Nullable String getName() {
135+
return name;
136+
}
133137

134138
public Class<T> getType() {
135139
return type.getC();

src/main/java/ch/njol/skript/command/ScriptCommand.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import ch.njol.skript.lang.Expression;
99
import ch.njol.skript.lang.SkriptParser;
1010
import ch.njol.skript.lang.Trigger;
11+
import ch.njol.skript.lang.Variable;
1112
import ch.njol.skript.lang.VariableString;
13+
import ch.njol.skript.lang.parser.ParserInstance;
1214
import ch.njol.skript.lang.util.SimpleEvent;
1315
import ch.njol.skript.lang.util.SimpleLiteral;
1416
import ch.njol.skript.localization.Language;
@@ -23,6 +25,7 @@
2325
import ch.njol.skript.util.Utils;
2426
import ch.njol.skript.util.chat.BungeeConverter;
2527
import ch.njol.skript.util.chat.MessageComponent;
28+
import ch.njol.skript.variables.HintManager;
2629
import ch.njol.skript.variables.Variables;
2730
import ch.njol.util.StringUtils;
2831
import com.google.common.base.Preconditions;
@@ -204,8 +207,24 @@ public ScriptCommand(
204207
this.pattern = pattern;
205208
this.arguments = arguments;
206209

207-
trigger = new Trigger(script, "command /" + name, new SimpleEvent(), ScriptLoader.loadItems(node));
208-
trigger.setLineNumber(node.getLine());
210+
HintManager hintManager = ParserInstance.get().getHintManager();
211+
try {
212+
hintManager.enterScope(false);
213+
for (Argument<?> argument : arguments) {
214+
String hintName = argument.getName();
215+
if (hintName == null) {
216+
continue;
217+
}
218+
if (!argument.isSingle()) {
219+
hintName += Variable.SEPARATOR + "*";
220+
}
221+
hintManager.set(hintName, argument.getType());
222+
}
223+
trigger = new Trigger(script, "command /" + name, new SimpleEvent(), ScriptLoader.loadItems(node));
224+
trigger.setLineNumber(node.getLine());
225+
} finally {
226+
hintManager.exitScope();
227+
}
209228

210229
bukkitCommand = setupBukkitCommand();
211230
}

src/main/java/ch/njol/skript/effects/EffChange.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import ch.njol.skript.lang.SyntaxStringBuilder;
1616
import ch.njol.skript.lang.Variable;
1717
import ch.njol.skript.util.LiteralUtils;
18+
import ch.njol.skript.variables.HintManager;
1819
import org.skriptlang.skript.lang.script.ScriptWarning;
1920
import org.bukkit.event.Event;
2021
import org.jetbrains.annotations.Nullable;
@@ -180,6 +181,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
180181
flatAcceptedTypes[i] = type;
181182
}
182183

184+
// Hint handling for deleting
185+
if (changed instanceof Variable<?> variable && mode == ChangeMode.DELETE && HintManager.canUseHints(variable)) {
186+
// Remove type hints in this scope only for a deleted variable
187+
getParser().getHintManager().delete(variable);
188+
}
189+
183190
if (changer == null) { // Safe to reset/delete
184191
return true;
185192
}
@@ -271,13 +278,24 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
271278
}
272279

273280
// Print warning if attempting to save a non-serializable type in a global variable
274-
if (!variable.isLocal() && (mode == ChangeMode.SET || (variable.isList() && mode == ChangeMode.ADD))) {
275-
ClassInfo<?> changerInfo = Classes.getSuperClassInfo(changer.getReturnType());
276-
if (changerInfo.getC() != Object.class && changerInfo.getSerializer() == null && changerInfo.getSerializeAs() == null
277-
&& !SkriptConfig.disableObjectCannotBeSavedWarnings.value()
278-
&& getParser().isActive() && !getParser().getCurrentScript().suppressesWarning(ScriptWarning.VARIABLE_SAVE)) {
279-
Skript.warning(changerInfo.getName().withIndefiniteArticle() + " cannot be saved. That is, the contents of the variable "
280-
+ changed.toString(null, Skript.debug()) + " will be lost when the server stops.");
281+
if (mode == ChangeMode.SET || (variable.isList() && mode == ChangeMode.ADD)) {
282+
if (HintManager.canUseHints(variable)) { // Hint handling
283+
HintManager hintManager = getParser().getHintManager();
284+
Class<?>[] hints = changer.possibleReturnTypes();
285+
if (mode == ChangeMode.SET) { // Override existing hints in scope
286+
hintManager.set(variable, hints);
287+
} else {
288+
hintManager.add(variable, hints);
289+
}
290+
}
291+
if (!variable.isLocal()) {
292+
ClassInfo<?> changerInfo = Classes.getSuperClassInfo(changer.getReturnType());
293+
if (changerInfo.getC() != Object.class && changerInfo.getSerializer() == null && changerInfo.getSerializeAs() == null
294+
&& !SkriptConfig.disableObjectCannotBeSavedWarnings.value()
295+
&& getParser().isActive() && !getParser().getCurrentScript().suppressesWarning(ScriptWarning.VARIABLE_SAVE)) {
296+
Skript.warning(changerInfo.getName().withIndefiniteArticle() + " cannot be saved. That is, the contents of the variable "
297+
+ changed.toString(null, Skript.debug()) + " will be lost when the server stops.");
298+
}
281299
}
282300
}
283301
}

src/main/java/ch/njol/skript/effects/EffCopy.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import ch.njol.skript.lang.SkriptParser.ParseResult;
1414
import ch.njol.skript.lang.Variable;
1515
import ch.njol.skript.registrations.Classes;
16+
import ch.njol.skript.variables.HintManager;
1617
import ch.njol.skript.variables.Variables;
1718
import ch.njol.util.Kleenean;
1819
import org.bukkit.event.Event;
@@ -64,6 +65,16 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
6465
return false;
6566
}
6667
}
68+
69+
// set type hints for destination(s) if applicable
70+
Class<?>[] sourceHints = source.possibleReturnTypes();
71+
HintManager hintManager = getParser().getHintManager();
72+
for (Variable<?> destination : destinations) {
73+
if (HintManager.canUseHints(destination)) {
74+
hintManager.set(destination, sourceHints);
75+
}
76+
}
77+
6778
return true;
6879
}
6980

src/main/java/ch/njol/skript/effects/EffDoIf.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ch.njol.skript.effects;
22

3+
import ch.njol.skript.lang.ExecutionIntent;
34
import org.bukkit.event.Event;
45
import org.jetbrains.annotations.Nullable;
56

@@ -43,7 +44,19 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
4344
return false;
4445
}
4546
condition = Condition.parse(cond, "Can't understand this condition: " + cond);
46-
return effect != null && condition != null;
47+
48+
if (effect == null || condition == null) {
49+
return false;
50+
}
51+
52+
// handle special hint cases
53+
// if this statement could result in execution stopping, we want to pass up hints
54+
if (effect.executionIntent() instanceof ExecutionIntent.StopSections intent) {
55+
// copy up current hints
56+
getParser().getHintManager().mergeScope(0, intent.levels(), true);
57+
}
58+
59+
return true;
4760
}
4861

4962
@Override

src/main/java/ch/njol/skript/effects/EffReturn.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
8686
List<TriggerSection> innerSections = parser.getSectionsUntil((TriggerSection) handler);
8787
innerSections.add(0, (TriggerSection) handler);
8888
breakLevels = innerSections.size();
89+
if (parser.getCurrentSections().size() < breakLevels)
90+
breakLevels = -1;
8991
sectionsToExit = innerSections.stream()
9092
.filter(SectionExitHandler.class::isInstance)
9193
.map(SectionExitHandler.class::cast)
@@ -112,6 +114,8 @@ protected void execute(Event event) {
112114

113115
@Override
114116
public ExecutionIntent executionIntent() {
117+
if (breakLevels == -1)
118+
return ExecutionIntent.stopTrigger();
115119
return ExecutionIntent.stopSections(breakLevels);
116120
}
117121

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package ch.njol.skript.effects;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.doc.Description;
5+
import ch.njol.skript.doc.Example;
6+
import ch.njol.skript.doc.Name;
7+
import ch.njol.skript.doc.Since;
8+
import ch.njol.skript.lang.Effect;
9+
import ch.njol.skript.lang.Expression;
10+
import ch.njol.skript.lang.SkriptParser.ParseResult;
11+
import ch.njol.skript.registrations.Feature;
12+
import ch.njol.util.Kleenean;
13+
import org.bukkit.event.Event;
14+
import org.jetbrains.annotations.Nullable;
15+
import org.skriptlang.skript.lang.experiment.ExperimentData;
16+
import org.skriptlang.skript.lang.experiment.SimpleExperimentalSyntax;
17+
18+
@Name("Suppress Type Hints (Experimental)")
19+
@Description({
20+
"An effect to suppress local variable type hint errors for the syntax lines that follow this effect.",
21+
"NOTE: Suppressing type hints also prevents syntax from providing new type hints." +
22+
" For example, with type hints suppressed, 'set {_x} to true' would not provide 'boolean' as a type hint for '{_x}'"
23+
})
24+
@Example("""
25+
start suppressing local variable type hints
26+
# potentially unsafe code goes here
27+
stop suppressing local variable type hints
28+
""")
29+
@Since("INSERT VERSION")
30+
public class EffSuppressTypeHints extends Effect implements SimpleExperimentalSyntax {
31+
32+
private static final ExperimentData EXPERIMENT_DATA = ExperimentData.createSingularData(Feature.TYPE_HINTS);
33+
34+
static {
35+
Skript.registerEffect(EffSuppressTypeHints.class,
36+
"[stop:un]suppress [local variable] type hints",
37+
"(start|:stop) suppressing [local variable] type hints");
38+
}
39+
40+
private boolean stop;
41+
42+
@Override
43+
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
44+
stop = parseResult.hasTag("stop");
45+
getParser().getHintManager().setActive(stop);
46+
return true;
47+
}
48+
49+
@Override
50+
protected void execute(Event event) { }
51+
52+
@Override
53+
public String toString(@Nullable Event event, boolean debug) {
54+
return (stop ? "stop" : "start") + " suppressing type hints";
55+
}
56+
57+
@Override
58+
public ExperimentData getExperimentData() {
59+
return EXPERIMENT_DATA;
60+
}
61+
62+
}

0 commit comments

Comments
 (0)