From ce133b9af547cec23c8fc63503ab2077014570d5 Mon Sep 17 00:00:00 2001 From: LadyCailin Date: Mon, 1 Nov 2021 13:56:49 +0100 Subject: [PATCH 01/70] Initial commit of generics --- .../Common/Annotations/AnnotationChecks.java | 7 +- .../com/laytonsmith/core/MainSandbox.java | 23 +++ .../compiler/analysis/StaticAnalysis.java | 28 ++-- .../core/constructs/CClassType.java | 158 ++++++++++++++++-- .../core/constructs/CFixedArray.java | 8 +- .../core/constructs/CSecureString.java | 58 +++---- .../generics/BoundaryConstraint.java | 23 +++ .../core/constructs/generics/Constraint.java | 82 +++++++++ .../generics/ConstraintLocation.java | 35 ++++ .../generics/ConstraintValidator.java | 54 ++++++ .../core/constructs/generics/Constraints.java | 38 +++++ .../generics/ConstructorConstraint.java | 53 ++++++ .../core/constructs/generics/ExactType.java | 31 ++++ .../generics/GenericDeclaration.java | 64 +++++++ .../generics/GenericParameters.java | 95 +++++++++++ .../generics/LeftHandGenericUse.java | 46 +++++ .../generics/LowerBoundConstraint.java | 41 +++++ .../generics/UnboundedConstraint.java | 32 ++++ .../generics/UpperBoundConstraint.java | 36 ++++ .../CRE/CREGenericConstraintException.java | 48 ++++++ .../core/functions/StringHandling.java | 23 ++- src/main/resources/docs/Generics | 140 ++++++++++++---- src/main/resources/docs/Typedefs | 52 ++++++ .../com/laytonsmith/core/GenericsTest.java | 56 +++++++ 24 files changed, 1113 insertions(+), 118 deletions(-) create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/BoundaryConstraint.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/Constraint.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/ConstraintLocation.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/ConstraintValidator.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/Constraints.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/ConstructorConstraint.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/ExactType.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/GenericDeclaration.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/GenericParameters.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/LeftHandGenericUse.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/LowerBoundConstraint.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/UnboundedConstraint.java create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/UpperBoundConstraint.java create mode 100644 src/main/java/com/laytonsmith/core/exceptions/CRE/CREGenericConstraintException.java create mode 100644 src/main/resources/docs/Typedefs create mode 100644 src/test/java/com/laytonsmith/core/GenericsTest.java diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java index 061c3ea356..22ded856fc 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java @@ -39,8 +39,11 @@ public static void checkForTypeInTypeofClasses() throws Exception { errors.add("TYPE is null? " + clazz.getClassName()); continue; } - if(!type.val().equals(clazz.getAnnotation(typeof.class).getValue("value"))) { - errors.add(clazz.getClassName() + "'s TYPE value is different than the typeof annotation on it"); + String classType = type.val().replaceAll("<.*>", ""); + if(!classType.equals(clazz.getAnnotation(typeof.class).getValue("value"))) { + errors.add(clazz.getClassName() + "'s TYPE value is different than the typeof annotation on it" + + " (expected " + clazz.getAnnotation(typeof.class).getValue("value") + " but" + + " got " + type.val() + ")"); } } catch (ReflectionUtils.ReflectionException ex) { errors.add(clazz.getClassName() + " needs to add the following:\n\t@SuppressWarnings(\"FieldNameHidesFieldInSuperclass\")\n" diff --git a/src/main/java/com/laytonsmith/core/MainSandbox.java b/src/main/java/com/laytonsmith/core/MainSandbox.java index ccdd230131..0cd30279ea 100644 --- a/src/main/java/com/laytonsmith/core/MainSandbox.java +++ b/src/main/java/com/laytonsmith/core/MainSandbox.java @@ -1,11 +1,34 @@ package com.laytonsmith.core; + + +import java.util.Arrays; + /** * This class is for testing concepts */ public class MainSandbox { + static class C { + + } + public static void main(String[] args) throws Exception { } + + public static void print(Object o) { + if(o == null) { + System.out.println("null"); + return; + } + if(o.getClass().isArray()) { + if(!o.getClass().isPrimitive()) { + System.out.println(Arrays.toString((Object[]) o)); + } + } else { + System.out.println(o); + } + } + } diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java index c6c05278aa..79b9951dc4 100644 --- a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java @@ -1,16 +1,5 @@ package com.laytonsmith.core.compiler.analysis; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Stack; -import java.util.TreeSet; - import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.CompilerEnvironment; @@ -26,15 +15,26 @@ import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.constructs.Variable; import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.CRE.CREException; +import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.functions.DataHandling; import com.laytonsmith.core.functions.Function; import com.laytonsmith.core.functions.IncludeCache; import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.core.telemetry.DefaultTelemetry; import com.laytonsmith.core.telemetry.Telemetry; + +import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Stack; +import java.util.TreeSet; /** * This class can be used to perform static analysis. @@ -431,7 +431,7 @@ public static void requireType(CClassType type, CClassType expected, Target t, Environment env, Set exceptions) { // Handle types that cause exceptions in the InstanceofUtil isInstanceof check. - if(type == expected || type == CClassType.AUTO || type == CNull.TYPE) { + if(type.equals(expected) || type == CClassType.AUTO || type == CNull.TYPE) { return; } if(type == CVoid.TYPE || expected == CVoid.TYPE || expected == CNull.TYPE) { @@ -461,7 +461,7 @@ public static void requireAnyType(CClassType type, CClassType[] expected, // Return if the type is instanceof any expected type. for(CClassType exp : expected) { - if(type == exp || type == CClassType.AUTO || type == CNull.TYPE + if(type.equals(exp) || type == CClassType.AUTO || type == CNull.TYPE || (type != CVoid.TYPE && exp != CVoid.TYPE && exp != CNull.TYPE) || InstanceofUtil.isInstanceof(type, exp, env)) { return; diff --git a/src/main/java/com/laytonsmith/core/constructs/CClassType.java b/src/main/java/com/laytonsmith/core/constructs/CClassType.java index a8dc40303b..167ab446a2 100644 --- a/src/main/java/com/laytonsmith/core/constructs/CClassType.java +++ b/src/main/java/com/laytonsmith/core/constructs/CClassType.java @@ -2,11 +2,19 @@ import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Pair; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.annotations.typeof; import com.laytonsmith.core.FullyQualifiedClassName; import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.constructs.generics.ConstraintLocation; +import com.laytonsmith.core.constructs.generics.Constraints; +import com.laytonsmith.core.constructs.generics.ExactType; +import com.laytonsmith.core.constructs.generics.GenericDeclaration; +import com.laytonsmith.core.constructs.generics.GenericParameters; +import com.laytonsmith.core.constructs.generics.LeftHandGenericUse; +import com.laytonsmith.core.constructs.generics.UnboundedConstraint; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREUnsupportedOperationException; @@ -17,10 +25,14 @@ import com.laytonsmith.core.objects.ObjectDefinitionNotFoundException; import com.laytonsmith.core.objects.ObjectDefinitionTable; import com.laytonsmith.core.objects.UserObject; + +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; @@ -30,7 +42,7 @@ import java.util.stream.Stream; /** - * A CClassType represent + * A CClassType represents a reference to a MethodScript class. */ @typeof("ms.lang.ClassType") @SuppressWarnings("checkstyle:overloadmethodsdeclarationorder") @@ -38,7 +50,7 @@ public final class CClassType extends Construct implements com.laytonsmith.core. public static final String PATH_SEPARATOR = FullyQualifiedClassName.PATH_SEPARATOR; - private static final Map CACHE = new HashMap<>(); + private static final ClassTypeCache CACHE = new ClassTypeCache(); // The only types that can be created here are the ones that don't have a real class associated with them, or the // TYPE value itself @@ -52,10 +64,15 @@ public final class CClassType extends Construct implements com.laytonsmith.core. */ private static final Mixed[] UNINITIALIZED = new Mixed[0]; + // Have to manually place this in the cache later + private static final GenericDeclaration CCLASS_TYPE_GENERIC_DECLARATION + = new GenericDeclaration(Target.UNKNOWN, new Constraints(ConstraintLocation.DEFINITION, + new UnboundedConstraint(Target.UNKNOWN, "T"))); + static { try { - TYPE = new CClassType("ms.lang.ClassType", Target.UNKNOWN); - AUTO = new CClassType("auto", Target.UNKNOWN); + TYPE = new CClassType("ms.lang.ClassType", Target.UNKNOWN, CCLASS_TYPE_GENERIC_DECLARATION); + AUTO = new CClassType("auto", Target.UNKNOWN, null); } catch (ClassNotFoundException e) { throw new Error(e); } @@ -68,7 +85,13 @@ public final class CClassType extends Construct implements com.laytonsmith.core. public static final CClassType[] EMPTY_CLASS_ARRAY = new CClassType[0]; static { - CACHE.put(FullyQualifiedClassName.forNativeClass(CClassType.class), TYPE); + CACHE.add(FullyQualifiedClassName.forNativeClass(CClassType.class), + GenericParameters.start(CCLASS_TYPE_GENERIC_DECLARATION) + .addParameter(CClassType.TYPE, + new LeftHandGenericUse(CClassType.TYPE, Target.UNKNOWN, + new Constraints(ConstraintLocation.LHS, + new ExactType(Target.UNKNOWN, CClassType.TYPE)))) + .build(), TYPE); } private final boolean isTypeUnion; @@ -94,21 +117,41 @@ public final class CClassType extends Construct implements com.laytonsmith.core. */ private final SortedSet types = new TreeSet<>(); + private final GenericDeclaration genericDeclaration; + private final GenericParameters genericParameters; + /** * Returns the singular instance of CClassType that represents this type. * *

IMPORTANT: The type MUST be fully qualified AND exist as a real, instantiable class, or this will cause * errors. The only time this method is preferred vs {@link #get(com.laytonsmith.core.FullyQualifiedClassName)} is * when used to define the TYPE value. The native class must also be provided at the same time, which is used - * for various operations to increase efficiency when dealing with native classes. + * for various operations to increase efficiency when dealing with native classes. If the type is defined with + * generics, use {@link #getWithGenericDefinition(Class, GenericDeclaration)}. * * Unlike the other getters, this will not throw a ClassNotFoundException, it will instead throw an Error. * @param type * @return */ public static CClassType get(Class type) { + return getWithGenericDefinition(type, null); + } + + /** + * Returns the singular instance of CClassType that represents this type. + * + *

IMPORTANT: The type MUST be fully qualified AND exist as a real, instantiable class, or this will cause + * errors. The only time this method is preferred vs {@link #get(com.laytonsmith.core.FullyQualifiedClassName)} is + * when used to define the TYPE value. The native class must also be provided at the same time, which is used + * for various operations to increase efficiency when dealing with native classes. + * + * Unlike the other getters, this will not throw a ClassNotFoundException, it will instead throw an Error. + * @param type + * @return + */ + public static CClassType getWithGenericDefinition(Class type, GenericDeclaration generics) { try { - CClassType t = get(FullyQualifiedClassName.forNativeClass(type)); + CClassType t = get(FullyQualifiedClassName.forNativeClass(type), generics); t.nativeClass = type; return t; } catch (ClassNotFoundException ex) { @@ -116,17 +159,29 @@ public static CClassType get(Class type) { } } + /** + * Returns the "naked class type". This is the + * @param type + * @return + * @throws ClassNotFoundException + */ + public static CClassType getNakedClassType(FullyQualifiedClassName type) + throws ClassNotFoundException { + return CACHE.getNakedClassType(type); + } + /** * Returns the singular instance of CClassType that represents this type. * * @param type * @return */ - public static CClassType get(FullyQualifiedClassName type) throws ClassNotFoundException { + public static CClassType get(FullyQualifiedClassName type, GenericParameters generics) + throws ClassNotFoundException { assert type != null; - CClassType ctype = CACHE.get(type); + CClassType ctype = CACHE.get(); if(ctype == null) { - ctype = new CClassType(type, Target.UNKNOWN, false); + ctype = new CClassType(type, Target.UNKNOWN, false, generics); CACHE.put(type, ctype); } return ctype; @@ -149,7 +204,7 @@ public static CClassType get(FullyQualifiedClassName... types) throws ClassNotFo = FullyQualifiedClassName.forFullyQualifiedClass(StringUtils.Join(t, "|", e -> e.getFQCN())); CClassType ctype = CACHE.get(type); if(ctype == null) { - ctype = new CClassType(type, Target.UNKNOWN, false); + ctype = new CClassType(type, Target.UNKNOWN, false, null); CACHE.put(type, ctype); } return ctype; @@ -179,7 +234,7 @@ public static CClassType get(CClassType... types) throws ClassNotFoundException */ public static CClassType defineClass(FullyQualifiedClassName fqcn) { try { - CClassType type = new CClassType(fqcn, Target.UNKNOWN, true); + CClassType type = new CClassType(fqcn, Target.UNKNOWN, true, null); CACHE.put(fqcn, type); return type; } catch (ClassNotFoundException ex) { @@ -192,8 +247,21 @@ public static CClassType defineClass(FullyQualifiedClassName fqcn) { * @param type This must be the fully qualified string name. * @param t */ - private CClassType(String type, Target t) throws ClassNotFoundException { - this(FullyQualifiedClassName.forFullyQualifiedClass(type), t, false); + private CClassType(String type, Target t, GenericDeclaration genericDeclaration) throws ClassNotFoundException { + this(FullyQualifiedClassName.forFullyQualifiedClass(type), t, false, genericDeclaration); + } + + private static String formatName(FullyQualifiedClassName type, GenericDeclaration generics) { + if(generics == null) { + return type.getFQCN(); + } else { + StringBuilder b = new StringBuilder(); + b.append(type.getFQCN()); + b.append("<"); + b.append(generics.toString()); + b.append(">"); + return b.toString(); + } } /** @@ -204,10 +272,13 @@ private CClassType(String type, Target t) throws ClassNotFoundException { * @param newDefinition If true, this function MUST NOT throw a ClassNotFoundException. */ @SuppressWarnings("ConvertToStringSwitch") - private CClassType(FullyQualifiedClassName type, Target t, boolean newDefinition) throws ClassNotFoundException { - super(type.getFQCN(), ConstructType.CLASS_TYPE, t); + private CClassType(FullyQualifiedClassName type, Target t, boolean newDefinition, GenericDeclaration genericDeclaration) + throws ClassNotFoundException { + super(formatName(type, genericDeclaration), ConstructType.CLASS_TYPE, t); isTypeUnion = type.isTypeUnion(); fqcn = type; + this.genericDeclaration = genericDeclaration; + this.genericParameters = new GenericParameters(this); if(isTypeUnion) { // Split them out types.addAll(Stream.of(type.getFQCN().split("\\|")) @@ -536,10 +607,23 @@ public Version since() { return MSVersion.V3_3_1; } + /** + * Returns the fully qualified class name for the class. Note that this is just the name of the class, not the + * complete type definition. See {@link #getTypeDefinition}. + * @return ms.lang.ClassType for instance. + */ public FullyQualifiedClassName getFQCN() { return fqcn; } + /** + * Returns the type definition, including generic definitions. + * @return ms.lang.ClassType>T< for instance. + */ + public String getTypeDefinition() { + return val(); + } + public boolean isEnum() { if("ms.lang.enum".equals(fqcn.getFQCN())) { // By default, this returns true when something is instanceof a thing, but in this case, we don't want @@ -646,4 +730,46 @@ public boolean getBooleanValue(Target t) { return true; } + /** + * Returns the generic declaration on the class itself. + * + * @return null if no generics were defined on this class, or else the {@link GenericDeclaration} for this ClassType. + */ + public GenericDeclaration getGenericDeclaration() { + return genericDeclaration; + } + + private static class ClassTypeCache { + + private Map, CClassType> cache; + + public ClassTypeCache() { + cache = Collections.synchronizedMap(new HashMap<>()); + } + + public void add(FullyQualifiedClassName fqcn, GenericParameters parameters, CClassType type) { + cache.put(new Pair<>(fqcn, parameters), type); + } + + public List getGenericsByFQCN(FullyQualifiedClassName fqcn) { + List ret = new ArrayList<>(); + Set> keySet = cache.keySet(); + synchronized(cache) { + for(Pair lhs : keySet) { + if(lhs.getKey().equals(fqcn)) { + ret.add(lhs.getValue()); + } + } + } + return ret; + } + + public CClassType getNakedClassType(FullyQualifiedClassName fqcn) { + return cache.get(new Pair<>(fqcn, null)); + } + + public void get(FullyQualifiedClassName fqcn, GenericParameters declaration) { + cache.get(new Pair<>(fqcn, declaration)); + } + } } diff --git a/src/main/java/com/laytonsmith/core/constructs/CFixedArray.java b/src/main/java/com/laytonsmith/core/constructs/CFixedArray.java index 2d2613b483..e069070086 100644 --- a/src/main/java/com/laytonsmith/core/constructs/CFixedArray.java +++ b/src/main/java/com/laytonsmith/core/constructs/CFixedArray.java @@ -5,6 +5,10 @@ import com.laytonsmith.annotations.typeof; import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.generics.ConstraintLocation; +import com.laytonsmith.core.constructs.generics.Constraints; +import com.laytonsmith.core.constructs.generics.UnboundedConstraint; +import com.laytonsmith.core.constructs.generics.GenericDeclaration; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREIndexOverflowException; import com.laytonsmith.core.exceptions.CRE.CREUnsupportedOperationException; @@ -26,7 +30,9 @@ public class CFixedArray extends Construct implements java.lang.Iterable, Booleanish, com.laytonsmith.core.natives.interfaces.Iterable { - public static final CClassType TYPE = CClassType.get(CFixedArray.class); + public static final CClassType TYPE = CClassType.getWithGenericDefinition(CFixedArray.class, + new GenericDeclaration(Target.UNKNOWN, new Constraints(ConstraintLocation.DEFINITION, + new UnboundedConstraint(Target.UNKNOWN, "T")))); private Mixed[] data; private CClassType allowedType; diff --git a/src/main/java/com/laytonsmith/core/constructs/CSecureString.java b/src/main/java/com/laytonsmith/core/constructs/CSecureString.java index 69f1e94462..208e89b573 100644 --- a/src/main/java/com/laytonsmith/core/constructs/CSecureString.java +++ b/src/main/java/com/laytonsmith/core/constructs/CSecureString.java @@ -5,7 +5,14 @@ import com.laytonsmith.annotations.typeof; import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.exceptions.CRE.CREFormatException; -import com.laytonsmith.core.exceptions.CRE.CREIndexOverflowException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -16,20 +23,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; /** * * @author cailin */ @typeof("ms.lang.secure_string") -public class CSecureString extends CString { +public class CSecureString extends Construct { @SuppressWarnings("FieldNameHidesFieldInSuperclass") public static final CClassType TYPE = CClassType.get(CSecureString.class); @@ -40,20 +40,20 @@ public class CSecureString extends CString { private int actualLength; public CSecureString(char[] val, Target t) { - super("**secure string**", t); + super("**secure string**", ConstructType.STRING, t); init(); construct(ArrayUtils.charToBytes(val)); } public CSecureString(CArray val, Target t) { - super("**secure string**", t); + super("**secure string**", ConstructType.STRING, t); init(); construct(CArrayToByteArray(val, t)); } // duplicate constructor private CSecureString(byte[] encrypted, Cipher decrypter, int encLength, int actualLength, Target t) { - super("**secure string**", t); + super("**secure string**", ConstructType.STRING, t); init(); this.encrypted = encrypted; this.decrypter = decrypter; @@ -123,12 +123,17 @@ public CArray getDecryptedCharCArray() { return carray; } + @Override + public boolean isDynamic() { + return false; + } + @Override public String docs() { return "A secure_string is a string which cannot normally be toString'd, and whose underlying representation" + " is encrypted in memory. This should be used for storing passwords or other sensitive data which" - + " should in no cases be stored in plain text. Since this extends string, it can generally be used in" - + " place of a string, and when done so, cannot accidentally be exposed (via logs or exception messages," + + " should in no cases be stored in plain text. In this way, it cannot accidentally be exposed" + + " (via logs or exception messages," + " or other accidental exposure) unless it is specifically instructed to decrypt and switch to a char" + " array. While this cannot by itself ensure security of the value, it can help prevent most accidental" + " exposures of data by intermediate code. When exported as a string (or imported as a string) other" @@ -152,26 +157,6 @@ public CClassType[] getInterfaces() { return CClassType.EMPTY_CLASS_ARRAY; } - @Override - public long size() { - return 0; - } - - @Override - public CString clone() throws CloneNotSupportedException { - return this; - } - - @Override - public Construct get(int index, Target t) { - throw new CREIndexOverflowException("Secure strings cannot be iterated", t); - } - - @Override - public Construct slice(int begin, int end, Target t) { - throw new CREIndexOverflowException("Secure strings cannot be sliced", t); - } - private static volatile boolean initialized = false; private static void init() { if(!initialized) { @@ -228,9 +213,4 @@ private static void fixKeyLength() { } } - @Override - public CSecureString duplicate() { - return new CSecureString(encrypted, decrypter, encLength, actualLength, getTarget()); - } - } diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/BoundaryConstraint.java b/src/main/java/com/laytonsmith/core/constructs/generics/BoundaryConstraint.java new file mode 100644 index 0000000000..a93ac23870 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/BoundaryConstraint.java @@ -0,0 +1,23 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.Auto; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.CRE.CREGenericConstraintException; + +/** + * A TypeConstraint is a value that has a ClassType boundary. + */ +public abstract class BoundaryConstraint extends Constraint { + protected CClassType bound; + + protected BoundaryConstraint(Target t, String typename, CClassType bound) { + super(t, typename); + if(bound == Auto.TYPE) { + throw new CREGenericConstraintException("Cannot use auto types on " + getConstraintName() + + " constraints", t); + } + this.bound = bound; + } + +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/Constraint.java b/src/main/java/com/laytonsmith/core/constructs/generics/Constraint.java new file mode 100644 index 0000000000..3d91e57b9c --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/Constraint.java @@ -0,0 +1,82 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.Target; + +import java.util.EnumSet; + +/** + * A Constraint is a single part of the general declaration. For instance, in the declaration + * T extends Number & new T(A, B, C), this has two constraints, the first being + * T extends Number and the second being new T(A, B, C). The first is an UpperBoundConstraint, + * and the second is a ConstructorConstraint. A third type is a LowerBoundConstraint, which is for + * constraints defined with T super Number, and finally, an exact constraint, which is simply + * a ClassType. + *

+ * In general, two different Constraints may be contradictory, and when used in combination, erroneous. The solver + * for this isn't built in to the Constraint class however, since you need to know all constraints in the generic + * to determine if the combination is erroneous. Individually, a constraint cannot itself be erroneous. + * + * Constraints can be placed in 3 different locations. + *

    + *
  • Definitions - That is, a class or method definition
  • + *
  • LHS - Function parameter definitions or LHS of an assignment
  • + *
  • RHS - Function parameters, RHS of an assignment
  • + *
+ * + * Different constraints are valid in different locations, though the RHS can only contain a concrete class, and so + * isn't grouped as part of the Constraint heirarchy itself. + * + * For the Definition site and LHS sites though, some constraints are simply not valid at all, and others must be used + * as a wildcard. + */ +public abstract class Constraint { + private String typename; + private boolean isWildcard; + private Target target; + + /** + * Constructs a new constraint. + * @param constraintName The name of the constraint, such as T or ? + */ + protected Constraint(Target t, String constraintName) { + this.typename = constraintName; + isWildcard = this.typename.equals("?"); + } + + /** + * Returns the name of the type, for instance T. If defined as a wildcard, this will be ? + * @return + */ + public String getTypeName() { + return this.typename; + } + + /** + * Returns an EnumSet which contains the locations where this is valid to be defined at. + * @return + */ + public abstract EnumSet validLocations(); + + /** + * This returns the name of the type of constraint, for instance "lower bound constraint". This is useful for + * identifying the constraint type in error messages. + * @return + */ + public abstract String getConstraintName(); + + /** + * Returns true if the type was defined as a wildcard. + * @return + */ + public boolean isWildcard() { + return isWildcard; + } + + /** + * Returns the code target where this constraint was defined. + * @return + */ + public Target getTarget() { + return target; + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/ConstraintLocation.java b/src/main/java/com/laytonsmith/core/constructs/generics/ConstraintLocation.java new file mode 100644 index 0000000000..229982d9b8 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/ConstraintLocation.java @@ -0,0 +1,35 @@ +package com.laytonsmith.core.constructs.generics; + +/** + * This enum represents where in the code the Constraint type may be placed. + */ +public enum ConstraintLocation { + /** + * The definition point, that is, the <T> in class A<T> or + * >T< T method(){...} + */ + DEFINITION("generic type definition"), + /** + * The left hand side of a statement, either the left hand of an assignment, or the parameter definition + * of a function call. + */ + LHS("left hand type definition"), + /** + * The right hand side of a statement, either the right hand of an assignment, or the parameter sent to + * a function. + */ + RHS("right hand type definition"); + + /** + * The value here should flow with the sentence "at the location of the ..." + */ + private final String locationName; + + private ConstraintLocation(String locationName) { + this.locationName = locationName; + } + + public String getLocationName() { + return this.locationName; + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/ConstraintValidator.java b/src/main/java/com/laytonsmith/core/constructs/generics/ConstraintValidator.java new file mode 100644 index 0000000000..2ec12fa253 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/ConstraintValidator.java @@ -0,0 +1,54 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.CRE.CREGenericConstraintException; + +import java.util.List; + +public class ConstraintValidator { + + private ConstraintValidator(){} + + /** + * Validates and returns the typename for a set of constraints. + * @param constraints The constraint(s) to validate + * @return The typename, for instance T in T extends Number. + */ + public static String ValidateDefinition(List constraints) + throws CREGenericConstraintException { + for(Constraint c : constraints) { + if(!c.validLocations().contains(ConstraintLocation.DEFINITION)) { + throw new CREGenericConstraintException("The " + c.getConstraintName() + " constraint type cannot be" + + " used at the location of the " + ConstraintLocation.DEFINITION.getLocationName(), + c.getTarget()); + } + if(c.isWildcard()) { + throw new CREGenericConstraintException("Constraints cannot use wildcards at the definition site.", + c.getTarget()); + } + } + if(constraints.size() == 1) { + // Only 1 constraint is always valid + return constraints.get(0).getTypeName(); + } + throw new CREGenericConstraintException("Multiple constraints are not yet supported.", + constraints.get(0).getTarget()); + } + + public static void ValidateLHS(Target t, CClassType type, List c) + throws CREGenericConstraintException { + GenericDeclaration dec = type.getGenericDeclaration(); + if(dec.getParameterCount() != c.size()) { + throw new CREGenericConstraintException(type.getFQCN().getFQCN() + " defines " + dec.getParameterCount() + + " type parameter(s), but only found " + c.size(), t); + } + + List declarationConstraints = dec.getConstraints(); + for(int i = 0; i < declarationConstraints.size(); i++) { + Constraints definition = declarationConstraints.get(i); + Constraints lhs = c.get(i); + // Check that the LHS fits the bounds of the definition + } + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/Constraints.java b/src/main/java/com/laytonsmith/core/constructs/generics/Constraints.java new file mode 100644 index 0000000000..6afac20444 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/Constraints.java @@ -0,0 +1,38 @@ +package com.laytonsmith.core.constructs.generics; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.List; + +/** + * A Constraints object contains the full list of Constraints for a given type parameter. + */ +public class Constraints extends AbstractList { + + private final List list; + private final String typename; + + public Constraints(ConstraintLocation location, Constraint... constraints) { + this.list = Arrays.asList(constraints); + typename = ConstraintValidator.ValidateDefinition(this.list, location); + } + + @Override + public Constraint get(int index) { + return list.get(index); + } + + @Override + public int size() { + return list.size(); + } + + /** + * Returns the name of the type. T for instance, or ? if this is a wildcard. + * @return + */ + public String getTypeName() { + return typename; + } + +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/ConstructorConstraint.java b/src/main/java/com/laytonsmith/core/constructs/generics/ConstructorConstraint.java new file mode 100644 index 0000000000..6b4a2d1297 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/ConstructorConstraint.java @@ -0,0 +1,53 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +/** + * A ConstructorConstraint is defined with new T(), optionally providing + * types such as new T(A, B, C) where A, B, C are concrete types, and + * T is the type variable. + * This requires that the class provided by the use-site contains a constructor of the + * specified type. + */ +public class ConstructorConstraint extends Constraint { + + private final List types; + + public ConstructorConstraint(Target t, String typename, CClassType... types) { + super(t, typename); + this.types = Arrays.asList(types); + } + + public List getTypes() { + return new ArrayList<>(this.types); + } + + @Override + public String toString() { + return "new " + getTypeName() + "(" + + StringUtils.Join(types, + ", ", + ", ", + ", ", + "", + item -> item.getFQCN().getFQCN()) + + ")"; + } + + @Override + public EnumSet validLocations() { + return EnumSet.allOf(ConstraintLocation.class); + } + + @Override + public String getConstraintName() { + return "constructor constraint"; + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/ExactType.java b/src/main/java/com/laytonsmith/core/constructs/generics/ExactType.java new file mode 100644 index 0000000000..2c54c72a04 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/ExactType.java @@ -0,0 +1,31 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +import java.util.EnumSet; + +public class ExactType extends Constraint { + + private final CClassType type; + + public ExactType(Target t, CClassType type) { + super(t, type.getFQCN().getFQCN()); + this.type = type; + } + + @Override + public EnumSet validLocations() { + return EnumSet.of(ConstraintLocation.LHS, ConstraintLocation.RHS); + } + + @Override + public String getConstraintName() { + return type.getFQCN().getFQCN(); + } + + public CClassType getType() { + return type; + } + +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/GenericDeclaration.java b/src/main/java/com/laytonsmith/core/constructs/generics/GenericDeclaration.java new file mode 100644 index 0000000000..e0cb9afef5 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/GenericDeclaration.java @@ -0,0 +1,64 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.PureUtilities.ObjectHelpers; +import com.laytonsmith.PureUtilities.ObjectHelpers.StandardField; +import com.laytonsmith.core.constructs.Target; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@StandardField +public class GenericDeclaration { + + private final List constraints; + + public GenericDeclaration(Target t, Constraints... constraints) { + this.constraints = Arrays.asList(constraints); + } + + /** + * Returns a list of the Constraints objects. Each Constraints object represents a single type parameter, though + * itself can contain multiple individual Constraint objects. + * @return + */ + public List getConstraints() { + return new ArrayList<>(constraints); + } + + public int getParameterCount() { + return constraints.size(); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + boolean joinComma = false; + for(Constraints c : constraints) { + if(joinComma) { + b.append(", "); + } + joinComma = true; + boolean join = false; + for(Constraint cc : c) { + if(join) { + b.append(" & "); + } + join = true; + b.append(cc.toString()); + } + } + return b.toString(); + } + + @Override + public boolean equals(Object that) { + return ObjectHelpers.DoEquals(this, that); + } + + @Override + public int hashCode() { + return ObjectHelpers.DoHashCode(this); + } + +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/GenericParameters.java b/src/main/java/com/laytonsmith/core/constructs/generics/GenericParameters.java new file mode 100644 index 0000000000..6bd79827f0 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/GenericParameters.java @@ -0,0 +1,95 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.PureUtilities.ObjectHelpers; +import com.laytonsmith.PureUtilities.ObjectHelpers.StandardField; +import com.laytonsmith.PureUtilities.Pair; +import com.laytonsmith.core.constructs.CClassType; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents the RHS of a class type definition with generics. For instance, in the statement + * new A<B>, this represents B. In general, this contains only concrete classes, however + * these classes themselves may have generic parameters, in which case they will contain the LHS information + * for those parameters. However, at a top line level, everything maps to concrete class types. + */ +@StandardField +public class GenericParameters { + + private GenericDeclaration genericDeclaration; + List> parameters; + + public static class GenericParametersBuilder1 { + GenericParameters p; + private GenericParametersBuilder1(GenericParameters p) { + this.p = p; + } + + /** + * Adds a new parameter. Each parameter consists of a CClassType, and optionally a LeftHandGenericUse. For + * instance, in the statement new A<B<? extends C>> where A is + * the class being constructed, with signature class A<T> + * and B is a concrete class itself with a single template parameter, and C being another class, then + * this method would be called with the parameters B and a new instance of the LeftHandGenericUse + * class representing the constraint ? extends C. + * @param type The concrete class type + * @param genericStatement The LHS generic statement for this parameter. This may be null if the type did not + * include a generic statement. + * @return this, for easy chaining. Use build() to construct the final object. + */ + public GenericParametersBuilder1 addParameter(CClassType type, LeftHandGenericUse genericStatement) { + p.parameters.add(new Pair<>(type, genericStatement)); + return this; + } + + /** + * Returns the fully constructed object. + * @return + */ + public GenericParameters build() { + return p; + } + } + + /** + * Begins construction of a new GenericParameters object, which represents the RHS of the generic declaration. The actual + * GenericDeclaration object is passed in in order to validate the types against the constraints. Each instance + * of a class which has a GenericDeclaration will have one of these objects in it, associated with that particular + * instance. This data is not lost after compilation, and types are reified for runtime use. + * @param declaration The generic declaration + */ + public static GenericParametersBuilder1 start(GenericDeclaration declaration) { + GenericParameters gp = new GenericParameters(); + gp.genericDeclaration = declaration; + return new GenericParametersBuilder1(gp); + } + + private GenericParameters() { + parameters = new ArrayList<>(); + } + + public GenericDeclaration getGenericDeclaration() { + return this.genericDeclaration; + } + + public List> getParameters() { + return parameters; + } + + @Override + public boolean equals(Object that) { + return ObjectHelpers.DoEquals(this, that); + } + + @Override + public int hashCode() { + return ObjectHelpers.DoHashCode(this); + } + + @Override + public String toString() { + return ObjectHelpers.DoToString(this); + } + +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/LeftHandGenericUse.java b/src/main/java/com/laytonsmith/core/constructs/generics/LeftHandGenericUse.java new file mode 100644 index 0000000000..85015296f2 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/LeftHandGenericUse.java @@ -0,0 +1,46 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.PureUtilities.ObjectHelpers; +import com.laytonsmith.PureUtilities.ObjectHelpers.StandardField; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +import java.util.Arrays; +import java.util.List; + +/** + * This class represents the generic parameters on the LHS. In general, this can contain several types of constraints, + * including the ExactType constraint. Unlike the RHS, this information is not required to be kept around post + * compilation, since the RHS is typechecked against this information, and then once confirmed to be correct, is no + * longer needed for dynamic use of the reified type. The RHS can in general contain LHS information though, + * particularly when the class specified on the RHS itself contains generics, in which case, the information within + * those parameters will be LHS information. + */ +@StandardField +public class LeftHandGenericUse { + + private final List constraints; + private final Target target; + + public LeftHandGenericUse(CClassType type, Target t, Constraints... constraints) { + this.target = t; + this.constraints = Arrays.asList(constraints); + ConstraintValidator.ValidateLHS(t, type, Arrays.asList(constraints)); + } + + + @Override + public boolean equals(Object that) { + return ObjectHelpers.DoEquals(this, that); + } + + @Override + public int hashCode() { + return ObjectHelpers.DoHashCode(this); + } + + @Override + public String toString() { + return ObjectHelpers.DoToString(this); + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/LowerBoundConstraint.java b/src/main/java/com/laytonsmith/core/constructs/generics/LowerBoundConstraint.java new file mode 100644 index 0000000000..65e7fea019 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/LowerBoundConstraint.java @@ -0,0 +1,41 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.CRE.CREGenericConstraintException; +import com.laytonsmith.core.natives.interfaces.Mixed; + +import java.util.EnumSet; + +/** + * A LowerBoundConstraint is defined with the super keyword, such as T super Number. + * In this case, Number is the lower bound. + */ +public class LowerBoundConstraint extends BoundaryConstraint { + + public LowerBoundConstraint(Target t, String typename, CClassType lowerBound) { + super(t, typename, lowerBound); + if(lowerBound.equals(Mixed.TYPE)) { + throw new CREGenericConstraintException("Cannot create a lower bound on mixed", t); + } + } + + public CClassType getLowerBound() { + return this.bound; + } + + @Override + public String toString() { + return getTypeName() + " super " + getLowerBound().getFQCN().getFQCN(); + } + + @Override + public EnumSet validLocations() { + return EnumSet.of(ConstraintLocation.LHS); + } + + @Override + public String getConstraintName() { + return "lower bound"; + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/UnboundedConstraint.java b/src/main/java/com/laytonsmith/core/constructs/generics/UnboundedConstraint.java new file mode 100644 index 0000000000..6acce55920 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/UnboundedConstraint.java @@ -0,0 +1,32 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.Target; + +import java.util.EnumSet; + +/** + * An ExactConstraint is a generic constraint which is a simple type. For instance, + * class M<T>, the constraint T is an ExactConstraint. Note that + * ExactConstraint isn't used in use-time, it's for declare time. + */ +public class UnboundedConstraint extends Constraint { + + public UnboundedConstraint(Target t, String typename) { + super(t, typename); + } + + @Override + public EnumSet validLocations() { + return EnumSet.of(ConstraintLocation.DEFINITION); + } + + @Override + public String getConstraintName() { + return "unbounded constraint"; + } + + @Override + public String toString() { + return getTypeName(); + } +} diff --git a/src/main/java/com/laytonsmith/core/constructs/generics/UpperBoundConstraint.java b/src/main/java/com/laytonsmith/core/constructs/generics/UpperBoundConstraint.java new file mode 100644 index 0000000000..86514255e9 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/constructs/generics/UpperBoundConstraint.java @@ -0,0 +1,36 @@ +package com.laytonsmith.core.constructs.generics; + +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +import java.util.EnumSet; + +/** + * An UpperBoundConstraint is defined with the extends keyword, such as T extends Number. + * In this case, Number is the upper bound. + */ +public class UpperBoundConstraint extends BoundaryConstraint { + + public UpperBoundConstraint(Target t, String typename, CClassType upperBound) { + super(t, typename, upperBound); + } + + public CClassType getUpperBound() { + return this.bound; + } + + @Override + public String toString() { + return getTypeName() + " extends " + getUpperBound().getFQCN().getFQCN(); + } + + @Override + public EnumSet validLocations() { + return EnumSet.allOf(ConstraintLocation.class); + } + + @Override + public String getConstraintName() { + return "upper bound"; + } +} diff --git a/src/main/java/com/laytonsmith/core/exceptions/CRE/CREGenericConstraintException.java b/src/main/java/com/laytonsmith/core/exceptions/CRE/CREGenericConstraintException.java new file mode 100644 index 0000000000..c6b08c7731 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/exceptions/CRE/CREGenericConstraintException.java @@ -0,0 +1,48 @@ +package com.laytonsmith.core.exceptions.CRE; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +/** + * + */ +@typeof("ms.lang.CastException") +public class CREGenericConstraintException extends CREException { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(CREGenericConstraintException.class); + + public CREGenericConstraintException(String msg, Target t) { + super(msg, t); + } + + public CREGenericConstraintException(String msg, Target t, Throwable cause) { + super(msg, t, cause); + } + + @Override + public String docs() { + return "This exception is thrown if a generic definition has an invalid constraint combination. For instance," + + " in the generic definition , the class would need to be both" + + " a superclass of mixed and a subclass of number, a state which is impossible to be in."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public CClassType[] getSuperclasses() { + return super.getSuperclasses(); + } + + @Override + public CClassType[] getInterfaces() { + return super.getInterfaces(); + } +} + diff --git a/src/main/java/com/laytonsmith/core/functions/StringHandling.java b/src/main/java/com/laytonsmith/core/functions/StringHandling.java index f8b9592a1b..f81b3d6ddd 100644 --- a/src/main/java/com/laytonsmith/core/functions/StringHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/StringHandling.java @@ -41,16 +41,19 @@ import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; import com.laytonsmith.core.exceptions.CRE.CRERangeException; import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.CancelCommandException; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.functions.Compiler.p; import com.laytonsmith.core.functions.DataHandling._string; import com.laytonsmith.core.functions.DataHandling.array; import com.laytonsmith.core.functions.DataHandling.g; -import com.laytonsmith.core.exceptions.CancelCommandException; -import com.laytonsmith.core.exceptions.ConfigCompileException; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.core.natives.interfaces.Sizeable; + import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.EnumSet; @@ -61,8 +64,6 @@ import java.util.List; import java.util.Locale; import java.util.Set; -import com.laytonsmith.core.natives.interfaces.Sizeable; -import java.lang.reflect.InaccessibleObjectException; import java.util.UUID; /** @@ -2450,9 +2451,10 @@ public Integer[] numArgs() { @Override public String docs() { return "secure_string {charArray|string} Constructs a secure_string from a given char array or string." - + " ---- A secure_string is a string which cannot normally be toString'd, and whose underlying representation" + + " ---- A secure_string is a pseudo-string which cannot normally be toString'd, and whose" + + " underlying representation" + " is encrypted in memory. This should be used for storing passwords or other sensitive data which" - + " should in no cases be stored in plain text. Since this extends string, it can generally be used in" + + " should in no cases be stored in plain text. It can generally be used in" + " place of a string, and when done so, cannot accidentally be exposed (via logs or exception messages," + " or other accidental exposure) unless it is specifically instructed to decrypt and switch to a char" + " array. While this cannot by itself ensure security of the value, it can help prevent most accidental" @@ -2476,10 +2478,7 @@ public ExampleScript[] examples() throws ConfigCompileException { new ExampleScript("Demonstrates compatibility with other functions", "@profile = array(\n" + "\tuser: 'username',\n\tpassword: secure_string('password')\n" + ");\n" - + "msg(@profile);"), - new ExampleScript("Demonstrates compatibility with string class", "string @sec = secure_string('password');" - + " // Not an error, because secure_string extends string\n" - + "msg(decrypt_secure_string(@sec));") + + "msg(@profile);") }; } } @@ -2532,7 +2531,7 @@ public Integer[] numArgs() { @Override public String docs() { - return "array {string} Decrypts a secure_string into a char array. To keep backwards compatibility with" + return "array {secure_string | string} Decrypts a secure_string into a char array. To keep backwards compatibility with" + " strings in general, this function also accepts normal strings, which are not decrypted, but" + " instead simply returned in the same format as if it were a secure_string." + " See the examples in {{function|secure_string}}."; diff --git a/src/main/resources/docs/Generics b/src/main/resources/docs/Generics index 20e5f4c44d..cda1185a1d 100644 --- a/src/main/resources/docs/Generics +++ b/src/main/resources/docs/Generics @@ -2,26 +2,22 @@ Generics are a way to make a class be able to specify a type which is to be determined later, and generically use that type no matter what specific type it is. An example of this is a collection, such as array, which can be typed -using generics so that only values of a specific type can be put into the array. +using generics so that only values of a specific type can be put into the array. ''Using'' generics is a basic concept, +but creating your own is a more advanced concept. == Using Defined Generics == The first place you'll likely encounter generics is simply when using classes which were defined with a generic parameter. A good first example is the array type. In general, let's consider a simple use without generics. <%CODE| -array @a = array(); // Using the array function -array @b = new array(); // Using the array type, and creating a new isntance of it +array @b = new array(); %> -Under the hood, the array function simply constructs a new instance of an array and returns it, so these two examples -are equivalent. - -In both cases, the array is implicitely constructed with an ''auto type parameter''. We can keep the same +In this case, the array is implicitely constructed with an ''auto type parameter''. We can keep the same behavior but make it explicit by doing this: <%CODE| -array @a = array(); -array @b = new array(); +array @a = new array(); %> In this case, we have passed the auto type to the array function/constructor. This tells the array @@ -70,7 +66,7 @@ class A {} In this extremely simple class, we have defined a class named A, and stated that it has a type parameter which we are calling T. (Note that in most examples, the letter T is used, but this doesn't have to be a single letter, -you can name it however you like.) This allows +you can name it however you like, just like how you can name a class whatever you like.) This allows us to use T within the class as if it were a real data type, and can use it for return values and parameter types. <%CODE| @@ -130,8 +126,8 @@ So far, we've only discussed ''unconstrained'' generics, but we can define the c then that constraint will be applied when considering the type that the object is being constructed with. There are multiple types of constraints, and 3 different rulesets for where these constraints can be used. -<%TAKENOTE|These terms are used throughout the rest of the article, and you should understand them well before reading -further%> +<%TAKENOTE|These terms (definition site, LHS and RHS) are used throughout the rest of the article, and you should +understand them well before reading further%> The three different locations are: # The definition site, that is, where class A is defined @@ -146,13 +142,21 @@ Type @a; // LHS @a = @value; // RHS proc _p(Type @n) {} // LHS -_p(@value); // @value is RHS, also the return value from _p is RHS +_p(@value); // @value is RHS, also the return value from _p is RHS if it were being assigned %> Depending on what constraint is used, and where the constraint is used, this gives restrictions in different places in the code. Some within the class where the generic is defined, and some at the use site of the class. Each is discussed below. +To put these into more concrete examples: + +<%CODE| +class A{} // is the definition site +A @a; // This is the LHS, this is what defines the type of the variable @a +@a = new A(); // This is the RHS, this is what defines the type and value that exists at runtime +%> + === Exact Type === An exact type is what we've shown above. But it's worth pointing out that if the class is defined with a type, you @@ -165,13 +169,12 @@ A @c = new A(); // Invalid! %> While int is a subclass of number, generic parameters do not inheret in the same way. If we consider the example class -A, we can see why if we think about the difference between the 3 places that constraints can be. - -Also, recall that the LHS and the RHS do not have to happen in the same place in a regular assignment either, if a -variable is forward declared, then the RHS might come later, and vary depending on what happens. +A, we can see why if we think about the difference between the LHS and RHS. Recall that the LHS and the RHS do not have +to happen in the same place in a regular assignment. If a variable is forward declared, then the RHS might come later, +and the specific value may vary depending on some runtime value. <%CODE| -A @a; // Forward declaration +A @a; // Forward declaration, @a is of type A if($input == "true") { @a = new A(); // Compile error } else { @@ -180,10 +183,10 @@ if($input == "true") { %> In the above example, you might think this should work, but it doesn't. But let's assume it did work, and consider -what happens lower in the code if we tried to use @a. +what happens lower in the code if we tried to use @a. Remember, the T defined in A would be defined as number. <%CODE| -@a->set(1.0); // 1.0 (a double) is a number. However, what if we had hit the top branch? +@a->set(1.0); // 1.0 (a double) is a number. However, what if $input had been true? %> Now, if $input is false, this should work fine, because A's T value is a double. But what happens if $input was true? @@ -198,7 +201,8 @@ This does not constrain you in what values you can pass when using the class tho A @a = new A(); int @value = 1234; @a->set(@value); // This works, despite @value being an int -int @return = @a->get(); // Compile error, we're trying to assign number to int +//int @return = @a->get(); // Compile error, we're trying to assign number to int +number @return = @a->get(); // This works though %> The only "catch" here is that T is of type number, so the return type is also a number, and even though we know we sent @@ -217,7 +221,7 @@ A @a = new A(); ==== LHS/RHS ==== However, we might not want this behavior, we might want to sometimes actually have a subclass in the generic parameter. -If so, we can use an boundary constraint, which is defined on the LHS. Let's consider an upper bound first. An upper +If so, we can use a boundary constraint, which is defined on the LHS. Let's consider an upper bound first. An upper bound is defined with the syntax ? extends Type. The question mark (called a wildcard) is there because we aren't actually defining a generic parameter here (like T), we're simply saying that the RHS for this must match this constraint. @@ -273,11 +277,14 @@ A @a; switch($input) { case "1": @a = new A(); case "2": @a = new A(); - cast "3": @a = new A(); + case "3": @a = new A(); } %> -This does change however, if the class definition has provided an upper bound. See below. +Now, depending on the input, we will have any potential subclass, and so when we call get(), we can only assume that +it's of type mixed. + +This does change slightly however, if the class definition has provided an upper bound. See below. ==== Upper Bound in Definition ==== @@ -289,7 +296,7 @@ class A {} %> Now, when we instantiate a new instance of A, we are required to provide a type which extends number, such as int or -double (or number itself), or another wildcard upper bound. +double (or number itself), or another wildcard upper bound, with the upper bound on that being number or lower. <%CODE| A @a = new A(); // Valid @@ -398,13 +405,14 @@ class MyClass { A @a = new A(); MyClass @newInstance = @a->getInstance(); -// Note on the LHS, we could have used "new()" if we don't generally care about the specific type, though that means +// Note on the LHS, we could have used ? if we don't generally care about the specific type, though that means // that we have to treat T as mixed when it's used on the RHS, unless there were additional constraints -A @dontcare = new A(); +A @dontcare = new A(); mixed @m = @dontcare->getInstance(); %> -We can also provide parameters to new() to state that there must be a public constructor which accepts the given types. +We can also provide parameters to new T() to state that there must be a public constructor which accepts +the specified types. <%CODE| // Assume X, Y, and Z are classes @@ -506,8 +514,8 @@ class A { %> When used in this way, T can only be used as a type of a variadic parameter in methods, and cannot be used as the type -of a field or of the return type of a method. When construct an instance of this type, we must specify the types on the -LHS, as well as the RHS. +of a field or of the return type of a method. When constructing an instance of this type, we must specify the types on +the LHS, as well as the RHS. <%CODE| A @a = new A(); @@ -584,18 +592,31 @@ number @n = @a->method(2); // T is number. This is also inferred, by finding the @a->method(3); // Now T is number (int is a subclass of number, so this is still valid code) %> +This is how the {{function|array}} function is defined, and why the following is the same: + +<%CODE| +array @a = array(); // T is inferred to be int +array @b = array(); // Explicit assignment of int to T +array @c = new array(); // Now we're using the new operator, not calling a function, so it has different syntax + +array @d = array(); // Different than @b, but this time we needed to be explicit +%> + == Inferred Type Parameters (Diamond Operator) == -In all the above examples, we have been explicit about the type passed to the RHS, but this isn't necessary. We can -instead on the RHS used the ''inferred type'', if that is correct, and simply omit the type on the RHS. +In all the above examples, we have been explicit about the type passed to the RHS, but this isn't always necessary. +We can instead on the RHS used the ''inferred type'', if that is correct, and simply omit the type on the RHS. <%CODE| class A {} A @a = new A<>(); // equivalent to new A() -A @a = new A(); // Different! Compiler warning is issued here, this is equivalent to new A() +A @a = new A(); // Different! Compiler error is issued here, this is equivalent to new A() %> +Note that unlike usual types, the generic type auto cannot be used in the same way, that is, <auto> +is not simply an untyped generic, but rather, a specific generic type which can contain any value. + Different types of constraints have different inference rules. <%CODE| @@ -617,7 +638,58 @@ class C {} C @c = new C(); // Inferred to new C() %> +== auto type with generics == + +In general, using auto types defers typechecking to runtime. However, with generics, this can lead to situations +where the typechecking error would end up happening in totally unrelated code, and so isn't allowed even though +auto types are generally a supported feature. Therefore, there are special rules +surrounding the use of auto in generic parameters. In general, it is allowed to be used with unbounded generics. + +<%CODE| +class A {} + +A @a = new A(); +%> + +However, most of the other constraints have problems when using auto types. Since auto doesn't generally +inheret from anything that we can determine at compile time (except mixed), it cannot be used in case there is an upper +bound on the LHS or definition site, where the upper bound is anything other than mixed. + +<%CODE| +A @a = new A(); // Compile error +%> + +It also can't be used in case of a lower bound, for any value. A lower bound of mixed could be theoretically fine, but +that is generally not allowed anyways, since only mixed is a superclass of mixed. + +It also can't be used when the class has a new constraint. + +<%CODE| +class A { + public T construct() { + return(new T()); + } +} + +A @a = new A(); // Compile error. This would cause construct to run `new auto()`. What would `new auto()` mean? +%> + +It also cannot be used on annotation constraints, since the constraints ensure that the class provides a certain +annotation. + +Finally, it cannot be used at the type of either lower or upper bounds. (T extends auto for instance). + +This doesn't stop the LHS of the assignment from being auto, however. This is allowed. + +<%CODE| +auto @a = new A(); +// Now @a is untyped at compile time, so no typechecking is done +int @i = @a->get(); // Works, but unchecked for compile errors and so not generally recommended +%> + +== Conclusion == + At a basic level, generics are fairly easy to understand, but especially with some of the constraints and more advanced techniques, it can be fairly difficult to understand, so don't be discouraged if you don't get everything in -this article at first. +this article at first. Try things out yourself, and run the code yourself to see how things change. diff --git a/src/main/resources/docs/Typedefs b/src/main/resources/docs/Typedefs new file mode 100644 index 0000000000..0690fab556 --- /dev/null +++ b/src/main/resources/docs/Typedefs @@ -0,0 +1,52 @@ +{{unimplemented}} + +Typedefs (type definitions) are a shorthand way of creating class aliases. Like use statements, they are +syntax sugar, and do not provide any functionality, only a shortcut during code writing. + +At their simplest, a typedef is just another name for a ClassType. + +<%CODE| +typedef S = string; +msg(typeof(S)); // ms.lang.string +%> + +By itself, this can be used to rename long classes or add disambiguation when dealing with classes with the same base +name that are in different packages. + +<%CODE| +typedef RLCNTIITT = ReallyLongClassNameThatIsInconvenientToType; + +typedef MyClass1 = org1.package.MyClass; +typedef MyClass2 = org2.package.MyClass; +%> + +Typedefs can also accept a GenericDeclaration with or without multiple constraints. + +<%CODE| +typedef T = ? extends object & MyInterface & new(A, B, C); +typedef U = number + +msg(typeof(T)); // GenericDeclaration +msg(typeof(U)); // GenericDeclaration + +T proc _returnsT() { + return(new T(new A(), new B(), new C())); +} +%> + +Typedefs can rely on other typedefs, though the relationship cannot be circular, and they are defined in the declaration +order. + +<%CODE| +typedef V = T; // invalid, T not defined +typedef T = int; +typedef U = T; +%> + +Typedefs are not actual objects and therefore may not be null. + +<%CODE| +typedef D = null; // compile error +%> + +Typedefs, like use statement are scoped to the file they are declared in, and must be declared at the top. \ No newline at end of file diff --git a/src/test/java/com/laytonsmith/core/GenericsTest.java b/src/test/java/com/laytonsmith/core/GenericsTest.java new file mode 100644 index 0000000000..69751cf643 --- /dev/null +++ b/src/test/java/com/laytonsmith/core/GenericsTest.java @@ -0,0 +1,56 @@ +package com.laytonsmith.core; + +import com.laytonsmith.core.constructs.CNumber; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.constructs.generics.ConstraintLocation; +import com.laytonsmith.core.constructs.generics.Constraints; +import com.laytonsmith.core.constructs.generics.GenericDeclaration; +import com.laytonsmith.core.constructs.generics.LowerBoundConstraint; +import com.laytonsmith.core.constructs.generics.UpperBoundConstraint; +import com.laytonsmith.core.exceptions.CRE.CREGenericConstraintException; +import com.laytonsmith.testing.StaticTest; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class GenericsTest { + public GenericsTest() { + + } + + @BeforeClass + public static void setUpClass() throws Exception { + StaticTest.InstallFakeServerFrontend(); + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() { + } + + @Test(expected = CREGenericConstraintException.class) + public void testBasicConstraintValidation() throws Exception { + // Lower bounds cannot be used on declarations + new GenericDeclaration(Target.UNKNOWN, new Constraints(ConstraintLocation.DEFINITION, + new LowerBoundConstraint(Target.UNKNOWN, "T", CNumber.TYPE))); + } + + @Test + public void testConstraintHasCorrectData() throws Exception { + UpperBoundConstraint ubc = new UpperBoundConstraint(Target.UNKNOWN, "T", CNumber.TYPE); + assertEquals("upper bound", ubc.getConstraintName()); + assertEquals("T", ubc.getTypeName()); + assertEquals("T extends ms.lang.number", ubc.toString()); + } +} From 5c4344d829c55f2d16ec5a43e23c6e99aa7352bc Mon Sep 17 00:00:00 2001 From: LadyCailin Date: Thu, 4 Nov 2021 01:15:23 +0100 Subject: [PATCH 02/70] Still working on changes to add environment in places --- .../bukkit/events/BukkitBlockEvents.java | 11 +- .../abstraction/events/MCSignChangeEvent.java | 3 +- .../java/com/laytonsmith/core/AliasCore.java | 14 +- .../java/com/laytonsmith/core/Procedure.java | 29 +- .../java/com/laytonsmith/core/Script.java | 4 +- .../laytonsmith/core/asm/LLVMFunction.java | 2 +- .../laytonsmith/core/constructs/CArray.java | 183 +++++----- .../core/constructs/CClassType.java | 228 ++++++++----- .../laytonsmith/core/constructs/CClosure.java | 42 ++- .../core/constructs/CFixedArray.java | 7 +- .../core/constructs/CIClosure.java | 19 +- .../core/constructs/CResource.java | 9 +- .../laytonsmith/core/constructs/CString.java | 13 +- .../core/constructs/Construct.java | 89 +++-- .../core/constructs/IVariable.java | 64 +++- .../core/constructs/IVariableList.java | 8 +- .../core/constructs/InstanceofUtil.java | 124 ++++--- .../generics/BoundaryConstraint.java | 30 +- .../core/constructs/generics/Constraint.java | 57 ++++ .../ConstraintToConstraintValidator.java | 9 + .../generics/ConstraintValidator.java | 54 ++- .../core/constructs/generics/Constraints.java | 99 +++++- .../core/constructs/generics/ExactType.java | 36 +- .../generics/GenericParameters.java | 44 ++- .../generics/LeftHandGenericUse.java | 15 +- .../generics/LowerBoundConstraint.java | 16 +- .../generics/UnboundedConstraint.java | 37 ++ .../generics/UpperBoundConstraint.java | 50 ++- .../core/events/AbstractEvent.java | 4 +- .../laytonsmith/core/events/BoundEvent.java | 30 +- .../com/laytonsmith/core/events/Event.java | 6 +- .../core/events/drivers/BlockEvents.java | 191 +++++------ .../core/events/drivers/CmdlineEvents.java | 25 +- .../core/events/drivers/EntityEvents.java | 201 +++++------ .../core/events/drivers/InventoryEvents.java | 137 ++++---- .../core/events/drivers/PlayerEvents.java | 150 ++++---- .../core/events/drivers/PluginEvents.java | 6 +- .../core/events/drivers/ServerEvents.java | 26 +- .../core/events/drivers/VehicleEvents.java | 20 +- .../core/events/drivers/WeatherEvents.java | 12 +- .../core/events/drivers/WorldEvents.java | 20 +- .../exceptions/CRE/AbstractCREException.java | 59 ++-- .../exceptions/ConfigRuntimeException.java | 54 ++- .../core/functions/AbstractFunction.java | 8 +- .../core/functions/ArrayHandling.java | 319 +++++++++--------- .../core/functions/BasicLogic.java | 6 +- .../laytonsmith/core/functions/BossBar.java | 51 +-- .../laytonsmith/core/functions/Compiler.java | 5 +- .../core/functions/CompositeFunction.java | 11 +- .../core/functions/ControlFlow.java | 49 ++- .../core/functions/DataHandling.java | 131 +++---- .../com/laytonsmith/core/functions/Debug.java | 18 +- .../core/functions/Enchantments.java | 28 +- .../core/functions/Environment.java | 161 ++++----- .../core/functions/Exceptions.java | 8 +- .../laytonsmith/core/functions/Function.java | 2 +- .../laytonsmith/core/functions/Marquee.java | 4 +- .../com/laytonsmith/core/functions/Math.java | 67 ++-- .../core/functions/ObjectManagement.java | 9 +- .../core/functions/ResourceManager.java | 5 +- .../laytonsmith/core/functions/Sandbox.java | 12 +- .../core/functions/Scoreboards.java | 139 ++++---- .../core/functions/StringHandling.java | 46 +-- .../laytonsmith/core/functions/Weather.java | 6 +- .../com/laytonsmith/core/functions/World.java | 104 +++--- .../AbstractMixedInterfaceRunner.java | 16 +- .../core/natives/interfaces/ArrayAccess.java | 11 +- .../core/natives/interfaces/Mixed.java | 34 +- .../core/objects/ObjectDefinition.java | 12 +- .../com/laytonsmith/tools/Interpreter.java | 39 ++- .../java/com/laytonsmith/tools/Manager.java | 38 ++- .../com/laytonsmith/core/GenericsTest.java | 57 +++- .../core/constructs/TestCClassType.java | 29 +- .../objects/ObjectDefinitionTableTest.java | 18 +- src/test/java/com/laytonsmith/testing/C.java | 3 +- 75 files changed, 2219 insertions(+), 1464 deletions(-) create mode 100644 src/main/java/com/laytonsmith/core/constructs/generics/ConstraintToConstraintValidator.java diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java index 6e8897268d..6b736bdcfd 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java @@ -42,6 +42,8 @@ import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.constructs.generics.GenericParameters; +import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; import org.bukkit.block.Block; import org.bukkit.event.block.BlockBreakEvent; @@ -367,7 +369,7 @@ public BukkitMCSignChangeEvent(SignChangeEvent e) { public static BukkitMCSignChangeEvent _instantiate(MCBlock sign, MCPlayer player, CArray signtext) { String[] text = new String[4]; for(int i = 0; i < signtext.size(); i++) { - text[i] = signtext.get(i, Target.UNKNOWN).toString(); + text[i] = signtext.get(i, Target.UNKNOWN, null).toString(); } return new BukkitMCSignChangeEvent(new SignChangeEvent(((BukkitMCBlock) sign).__Block(), ((BukkitMCPlayer) player)._Player(), text)); @@ -384,11 +386,12 @@ public CString getLine(int index) { } @Override - public CArray getLines() { - CArray retn = new CArray(Target.UNKNOWN); + public CArray getLines(Environment env) { + CArray retn = new CArray(Target.UNKNOWN, GenericParameters.start(CArray.TYPE) + .addParameter(CString.TYPE, null).build(), env); for(int i = 0; i < 4; i++) { - retn.push(new CString(pie.getLine(i), Target.UNKNOWN), Target.UNKNOWN); + retn.push(new CString(pie.getLine(i), Target.UNKNOWN), Target.UNKNOWN, env); } return retn; diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java index 645a813e9b..25b80096bf 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java @@ -4,6 +4,7 @@ import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CString; +import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.events.BindableEvent; public interface MCSignChangeEvent extends BindableEvent { @@ -18,5 +19,5 @@ public interface MCSignChangeEvent extends BindableEvent { void setLines(String[] lines); - CArray getLines(); + CArray getLines(Environment env); } diff --git a/src/main/java/com/laytonsmith/core/AliasCore.java b/src/main/java/com/laytonsmith/core/AliasCore.java index 184f90c054..8a7722435f 100644 --- a/src/main/java/com/laytonsmith/core/AliasCore.java +++ b/src/main/java/com/laytonsmith/core/AliasCore.java @@ -405,7 +405,7 @@ public final void reload(MCPlayer player, String[] settings, boolean firstLoad) this.autoIncludes, env, env.getEnvClasses(), compileExceptions); for(ConfigCompileException ex : compileExceptions) { ConfigRuntimeException.HandleUncaughtException(ex, "Compile error in script." - + " Compilation will attempt to continue, however.", player); + + " Compilation will attempt to continue, however.", player, env); } try { @@ -429,7 +429,7 @@ public final void reload(MCPlayer player, String[] settings, boolean firstLoad) if(s1.getCommandName().equalsIgnoreCase(s2.getCommandName())) { ConfigRuntimeException.HandleUncaughtException( new ConfigCompileException("Duplicate command defined. (First occurance found at " - + s1.getTarget() + ")", s2.getTarget()), "Duplicate command.", player); + + s1.getTarget() + ")", s2.getTarget()), "Duplicate command.", player, env); } } } @@ -810,11 +810,11 @@ public void compileMSA(List