Skip to content

Commit

Permalink
Fixing incorrect extension method iterator detection for Dynamic.
Browse files Browse the repository at this point in the history
fixing unification rules. (do not unify to Dynamic unless everything is Dynamic)
  • Loading branch information
m0rkeulv committed Jan 11, 2025
1 parent 7bd11bf commit 177153d
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ static ResultHolder handleIfStatement(

// No 'else' clause means the if results in a Void type.
if (null == tFalse) tFalse = SpecificHaxeClassReference.getVoid(ifStatement);

// TODO create rule use first on unknown
return HaxeTypeUnifier.unify(tTrue, tFalse, ifStatement, context.getScope().unificationRules).createHolder();
}

Expand Down Expand Up @@ -1941,6 +1941,8 @@ static ResultHolder handleIterable(
}

private static @Nullable ResultHolder searchForIteratorType(SpecificHaxeClassReference haxeClassReference, String iteratorName, HaxeForStatement parentForLoop) {
// ignore "Dynamic" as it can assign to anything and will in most cases incorrectly be matched with itterators for other types
if (haxeClassReference.isDynamic()) return null;
SpecificTypeReference typeReference = haxeClassReference.fullyResolveTypeDefAndUnwrapNullTypeReference();
if (typeReference instanceof SpecificHaxeClassReference resolvedClassReference) {
HaxeGenericResolver referenceGenericResolver = resolvedClassReference.getGenericResolver();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import java.util.List;
import java.util.Set;

import static com.intellij.plugins.haxe.model.type.UnificationRules.UNIFY_NULL;
import static com.intellij.plugins.haxe.model.type.UnificationRules.*;

public class HaxeTypeUnifier {
@NotNull
Expand Down Expand Up @@ -60,8 +60,10 @@ static public SpecificTypeReference unify(SpecificTypeReference a, SpecificTypeR
return a.withoutConstantValue();
}
// if dynamic and the result is not from constant (ex constant = null) use dynamic
if (a.isDynamic() && a.getConstant() == null) return a;
if (b.isDynamic() && b.getConstant() == null) return b;
if (rules == PREFER_DYNAMIC) {
if (a.isDynamic() && a.getConstant() == null) return a;
if (b.isDynamic() && b.getConstant() == null) return b;
}


// Using UnificationRules to make sure we only unify when assigned and not other cases like for instance
Expand Down Expand Up @@ -164,8 +166,13 @@ private static HaxeArgument unify(HaxeArgument a, HaxeArgument b, @NotNull Unifi

@NotNull
static public SpecificTypeReference unifyTypes(SpecificHaxeClassReference a, SpecificHaxeClassReference b, @NotNull PsiElement context, @NotNull UnificationRules rules) {
if (a.isDynamic()) return a.withoutConstantValue();
if (b.isDynamic()) return b.withoutConstantValue();
if(rules == PREFER_DYNAMIC) {
if (a.isDynamic()) return a.withoutConstantValue();
if (b.isDynamic()) return b.withoutConstantValue();
}else {
if (a.isDynamic()) return b.withoutConstantValue();
if (b.isDynamic()) return a.withoutConstantValue();
}

HaxeClassModel modelA = a.getHaxeClassModel();
if (modelA == null) return SpecificTypeReference.getDynamic(context);
Expand Down Expand Up @@ -237,6 +244,11 @@ else if (!a.isVoid() && b.isVoid()) {
return a;
}
}
// we prefer specific type here (makes it easier to unify Enums where some values have typeParameters and other not)
//TODO handle constraints
if (a.isTypeParameter() && !b.isTypeParameter()) return b;
if (b.isTypeParameter() && !a.isTypeParameter()) return a;

// @TODO: Do a proper unification
return SpecificTypeReference.getUnknown(a.getElementContext());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum UnificationRules {
DEFAULT,
PREFER_VOID,// typically when finding method return types
IGNORE_VOID, // typically when finding type for array and map literal (with guards)
PREFER_DYNAMIC,
// `var a = null; a = 1;` should unify to null<Int>,
// but `var a = 1 + null` should (most likely) never unify (illegal operation)
UNIFY_NULL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public void testOptionalFieldsHints() throws Exception {
public void testComplexMonomorphHints() throws Exception {
doTest(hintsProvider);
}
@Test
public void testValueExpressionHints() throws Exception {
doTest(hintsProvider);
}


}
125 changes: 125 additions & 0 deletions src/test/resources/testData/haxe/4.0.5/std/haxe/DynamicAccess.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (C)2005-2019 Haxe Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

package haxe;

import haxe.iterators.DynamicAccessIterator;
import haxe.iterators.DynamicAccessKeyValueIterator;

/**
DynamicAccess is an abstract type for working with anonymous structures
that are intended to hold collections of objects by the string key.
For example, these types of structures are often created from JSON.
Basically, it wraps `Reflect` calls in a `Map`-like interface.
**/
abstract DynamicAccess<T>(Dynamic<T>) from Dynamic<T> to Dynamic<T> {
/**
Creates a new structure.
**/
public inline function new()
this = {};

/**
Returns a value by specified `key`.
If the structure does not contain the given key, `null` is returned.
If `key` is `null`, the result is unspecified.
**/
@:arrayAccess
public inline function get(key:String):Null<T> {
#if js
return untyped this[key]; // we know it's an object, so we don't need a check
#else
return Reflect.field(this, key);
#end
}

/**
Sets a `value` for a specified `key`.
If the structure contains the given key, its value will be overwritten.
Returns the given value.
If `key` is `null`, the result is unspecified.
**/
@:arrayAccess
public inline function set(key:String, value:T):T {
#if js
return untyped this[key] = value;
#else
Reflect.setField(this, key, value);
return value;
#end
}

/**
Tells if the structure contains a specified `key`.
If `key` is `null`, the result is unspecified.
**/
public inline function exists(key:String):Bool
return Reflect.hasField(this, key);

/**
Removes a specified `key` from the structure.
Returns true, if `key` was present in structure, or false otherwise.
If `key` is `null`, the result is unspecified.
**/
public inline function remove(key:String):Bool
return Reflect.deleteField(this, key);

/**
Returns an array of `keys` in a structure.
**/
public inline function keys():Array<String>
return Reflect.fields(this);

/**
Returns a shallow copy of the structure
**/
public inline function copy():DynamicAccess<T>
return Reflect.copy(this);

/**
Returns an Iterator over the values of this `DynamicAccess`.
The order of values is undefined.
**/
public inline function iterator():DynamicAccessIterator<T> {
return new DynamicAccessIterator(this);
}

/**
Returns an Iterator over the keys and values of this `DynamicAccess`.
The order of values is undefined.
**/
public inline function keyValueIterator():DynamicAccessKeyValueIterator<T> {
return new DynamicAccessKeyValueIterator(this);
}
}
125 changes: 125 additions & 0 deletions src/test/resources/testData/haxe/4.3.6/std/haxe/DynamicAccess.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (C)2005-2019 Haxe Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

package haxe;

import haxe.iterators.DynamicAccessIterator;
import haxe.iterators.DynamicAccessKeyValueIterator;

/**
DynamicAccess is an abstract type for working with anonymous structures
that are intended to hold collections of objects by the string key.
For example, these types of structures are often created from JSON.
Basically, it wraps `Reflect` calls in a `Map`-like interface.
**/
abstract DynamicAccess<T>(Dynamic<T>) from Dynamic<T> to Dynamic<T> {
/**
Creates a new structure.
**/
public inline function new()
this = {};

/**
Returns a value by specified `key`.
If the structure does not contain the given key, `null` is returned.
If `key` is `null`, the result is unspecified.
**/
@:arrayAccess
public inline function get(key:String):Null<T> {
#if js
return untyped this[key]; // we know it's an object, so we don't need a check
#else
return Reflect.field(this, key);
#end
}

/**
Sets a `value` for a specified `key`.
If the structure contains the given key, its value will be overwritten.
Returns the given value.
If `key` is `null`, the result is unspecified.
**/
@:arrayAccess
public inline function set(key:String, value:T):T {
#if js
return untyped this[key] = value;
#else
Reflect.setField(this, key, value);
return value;
#end
}

/**
Tells if the structure contains a specified `key`.
If `key` is `null`, the result is unspecified.
**/
public inline function exists(key:String):Bool
return Reflect.hasField(this, key);

/**
Removes a specified `key` from the structure.
Returns true, if `key` was present in structure, or false otherwise.
If `key` is `null`, the result is unspecified.
**/
public inline function remove(key:String):Bool
return Reflect.deleteField(this, key);

/**
Returns an array of `keys` in a structure.
**/
public inline function keys():Array<String>
return Reflect.fields(this);

/**
Returns a shallow copy of the structure
**/
public inline function copy():DynamicAccess<T>
return Reflect.copy(this);

/**
Returns an Iterator over the values of this `DynamicAccess`.
The order of values is undefined.
**/
public inline function iterator():DynamicAccessIterator<T> {
return new DynamicAccessIterator(this);
}

/**
Returns an Iterator over the keys and values of this `DynamicAccess`.
The order of values is undefined.
**/
public inline function keyValueIterator():DynamicAccessKeyValueIterator<T> {
return new DynamicAccessKeyValueIterator(this);
}
}
Loading

0 comments on commit 177153d

Please sign in to comment.