Skip to content

Commit

Permalink
Fix index operations (#15)
Browse files Browse the repository at this point in the history
* Fix index operations

Currently index detection is done by looking for specific interface
implementations. This change enables the use of the
`DefaultMemberAttribute` to determine the indexer for a type.

* Change enumeration test to be order independant
  • Loading branch information
SeeminglyScience authored Apr 29, 2018
1 parent ed2115c commit f0dfb61
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 77 deletions.
87 changes: 27 additions & 60 deletions src/PSLambda/CompileVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -555,74 +555,41 @@ public object VisitIndexExpression(IndexExpressionAst indexExpressionAst)
indexExpressionAst.Index.Compile(this));
}

if (TryFindGenericInterface(source.Type, typeof(IList<>), out Type genericList))
{
return MakeIndex(
source,
genericList.GetProperty(Strings.DefaultIndexerPropertyName),
new[] { indexExpressionAst.Index.Compile(this) });
}

if (TryFindGenericInterface(source.Type, typeof(IDictionary<,>), out Type genericDictionary))
{
return MakeIndex(
source,
genericDictionary.GetProperty(Strings.DefaultIndexerPropertyName),
new[] { indexExpressionAst.Index.Compile(this) });
}
var defaultMember =
source.Type.GetCustomAttribute<DefaultMemberAttribute>(inherit: true);

if (TryFindGenericInterface(source.Type, typeof(IEnumerable<>), out Type genericEnumerable) ||
(source.Type.IsGenericType &&
source.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
if (defaultMember == null)
{
if (genericEnumerable == null)
{
genericEnumerable = source.Type;
}

Expression index;
try
{
index = Convert(indexExpressionAst.Index.Compile(this), typeof(int));
}
catch (InvalidOperationException e)
{
Errors.ReportParseError(indexExpressionAst.Index.Extent, e);
return Empty();
}

return Call(
typeof(Enumerable),
Strings.ElementAtOrDefaultMethodName,
genericEnumerable.GetGenericArguments(),
source,
index);
Errors.ReportParseError(
indexExpressionAst.Extent,
nameof(ErrorStrings.UnknownIndexer),
string.Format(
CultureInfo.CurrentCulture,
ErrorStrings.UnknownIndexer,
source.Type.FullName));
return Empty();
}

if (typeof(System.Collections.IList).IsAssignableFrom(source.Type))
{
return MakeIndex(
source,
ReflectionCache.IList_Item,
new[] { indexExpressionAst.Index.Compile(this) });
}
var index = indexExpressionAst.Index.Compile(this);
var indexerProperty = source.Type
.GetProperty(defaultMember.MemberName, new[] { index.Type });

if (typeof(System.Collections.IDictionary).IsAssignableFrom(source.Type))
if (indexerProperty == null)
{
return MakeIndex(
source,
ReflectionCache.IDictionary_Item,
new[] { indexExpressionAst.Index.Compile(this) });
Errors.ReportParseError(
indexExpressionAst.Extent,
nameof(ErrorStrings.UnknownIndexer),
string.Format(
CultureInfo.CurrentCulture,
ErrorStrings.UnknownIndexer,
source.Type.FullName));
return Empty();
}

Errors.ReportParseError(
indexExpressionAst.Extent,
nameof(ErrorStrings.UnknownIndexer),
string.Format(
CultureInfo.CurrentCulture,
ErrorStrings.UnknownIndexer,
source.Type.FullName));
return Empty();
return Property(
source,
indexerProperty,
index);
}

public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
Expand Down
10 changes: 6 additions & 4 deletions test/Loops.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ Describe 'basic loop functionality' {
three = 'four'
}

$sb = [System.Text.StringBuilder]::new()
$results = [System.Collections.Generic.List[string]]::new()
foreach($item in $hashtable) {
$sb.Append($item.Value.ToString())
$results.Add($item.Value.ToString())
}

return $sb.ToString()
return $results
}

$delegate.Invoke() | Should -Be twofour
$results = $delegate.Invoke()
$results | Should -Contain two
$results | Should -Contain four
}

It 'prioritizes IEnumerable<> over IDictionary' {
Expand Down
29 changes: 16 additions & 13 deletions test/MiscLanguageFeatures.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -206,28 +206,31 @@ Describe 'Misc Language Features' {
$delegate.Invoke() | Should -Be 'System'
}

It 'indexed IEnumerable<> are typed properly' {
It 'can index IList' {
$delegate = New-PSDelegate {
$strings = generic(
[System.Linq.Enumerable]::Select(
('test', 'test2', 'test3'),
[func[string, string]]{ ($string) => { $string }}),
[string], [string])

return $strings[1].EndsWith('2')
$list = [System.Collections.ArrayList]::new()
$list.AddRange([object[]]('one', 'two', 'three'))
return [string]$list[1] -eq 'two'
}

$delegate.Invoke() | Should -Be $true
}

It 'can index IList' {
It 'can index custom indexers' {
$delegate = New-PSDelegate {
$list = [System.Collections.ArrayList]::new()
$list.AddRange([object[]]('one', 'two', 'three'))
return [string]$list[1] -eq 'two'
$pso = [psobject]::AsPSObject([System.Text.StringBuilder]::new())
$pso.Methods['Append'].Invoke(@('testing'))
return $pso
}

$delegate.Invoke() | Should -Be $true
$result = $delegate.Invoke()
$result | Should -BeOfType System.Text.StringBuilder
$result.ToString() | Should -Be testing
}

It 'throws the correct message when indexer cannot be found' {
$expectedMsg = 'The indexer could not be determined for the type "System.Int32".'
{ New-PSDelegate { (10)[0] }} | Should -Throw $expectedMsg
}
}
}

0 comments on commit f0dfb61

Please sign in to comment.