docs: @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:booleanexpressioncomplexity"})
  	public static boolean canCast(
  			LightweightTypeReference fromType, LightweightTypeReference toType,
  			boolean enablePrimitiveWidening, boolean enableVoidMatchingNull,
  			boolean allowSynonyms) {
  		if (enableVoidMatchingNull) {
  			final boolean fromVoid = fromType == null || fromType.isPrimitiveVoid();
  			final boolean toVoid = toType == null || toType.isPrimitiveVoid();
  			if (fromVoid) {
  				return toVoid;
  			}
  			if (toVoid) {
  				return fromVoid;
  			}
  			assert fromType != null;
  			assert toType != null;
  		} else if ((fromType == null || toType == null)
  				|| (fromType.isPrimitiveVoid() != toType.isPrimitiveVoid())) {
  			return false;
  		}
  		final TypeConformanceComputationArgument conform = new TypeConformanceComputationArgument(
  				false, false, true, enablePrimitiveWidening, false, allowSynonyms);
  		if (((fromType.getType() instanceof JvmDeclaredType || fromType.isPrimitive())
  				// if one of the types is an interface and the other is a non final class
  				// (or interface) there always can be a subtype
  				&& (!isInterface(fromType) || isFinal(toType))
  				&& (!isInterface(toType) || isFinal(fromType))
  				&& (!toType.isAssignableFrom(fromType, conform))
  				&& (isFinal(fromType) || isFinal(toType)
  						|| isClass(fromType) && isClass(toType))
  				// no upcast
  				&& (!fromType.isAssignableFrom(toType, conform)))
  				|| (toType.isPrimitive() && !(fromType.isPrimitive() || fromType.isWrapper()))) {
  			return false;
  		}
  		return true;
  	}