Skip to content

Commit

Permalink
applied function argument type hints to sequential destructureing
Browse files Browse the repository at this point in the history
  • Loading branch information
jlangch committed Sep 13, 2023
1 parent 64ee3f0 commit 41dc249
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 40 deletions.
16 changes: 16 additions & 0 deletions doc/readme/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,22 @@ are available with Venice 1.11.x.

(sum (complex. 1 2) (complex. 5 8)))
```

Type hints with multi-arity functions:

```clojure
(do
(defn foo
([] 0)
([^:long x] x)
([^:long x ^:long y] (+ x y))
([^:long x ^:long y & xs] (apply + x y xs)))
(foo )
(foo 1)
(foo 1 2)
(foo 1 2 3 4 5))
```

For datatypes of the *core* namespace the namespace can be omitted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ else if (symName.equals("&")) {
final VncSymbol sym = (VncSymbol)symVal;
final VncVal bindVal = bindValsRest.first();
bindValsRest = bindValsRest.rest();
FunctionArgsTypeHints.validate(sym, bindVal);
bindings.add(new Var(sym, bindVal, Var.Scope.Local));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* __ __ _
* \ \ / /__ _ __ (_) ___ ___
* \ \/ / _ \ '_ \| |/ __/ _ \
* \ / __/ | | | | (_| __/
* \/ \___|_| |_|_|\___\___|
*
*
* Copyright 2017-2022 Venice
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.jlangch.venice.impl;

import com.github.jlangch.venice.AssertionException;
import com.github.jlangch.venice.impl.types.VncKeyword;
import com.github.jlangch.venice.impl.types.VncSymbol;
import com.github.jlangch.venice.impl.types.VncVal;
import com.github.jlangch.venice.impl.types.util.Types;
import com.github.jlangch.venice.impl.util.MetaUtil;
import com.github.jlangch.venice.impl.util.callstack.CallFrame;
import com.github.jlangch.venice.impl.util.callstack.WithCallStack;


public class FunctionArgsTypeHints {

public static void validate(
final VncSymbol sym,
final VncVal val
) {
validate(sym, val, getParamType(sym));
}

public static void validate(
final VncSymbol sym,
final VncVal val,
final VncKeyword typeMeta
) {
if (typeMeta != null) {
// check 'val' type against 'typeMeta'
if (!Types.isInstanceOf(typeMeta, val)) {
try (WithCallStack cs = new WithCallStack(callframe(sym))) {
throw new AssertionException(String.format(
"function argument type not compatible: arg-name=%s, arg-type=%s, expected-type=%s ",
sym.getSimpleName(),
Types.getType(val).toString(true),
typeMeta.toString(true)));
}
}
}
}

public static VncKeyword[] getParamTypes(final VncVal[] paramArr) {
final VncKeyword[] types = new VncKeyword[paramArr.length];

for(int ii=0; ii<paramArr.length; ii++) {
final VncVal p = paramArr[ii];
types[ii] = Types.isVncSymbol(p) ? getParamType((VncSymbol)p) : null;
}

return types;
}

public static VncKeyword getParamType(final VncSymbol param) {
final VncVal t = param.getMetaVal(MetaUtil.TYPE);
if (Types.isVncKeyword(t)) {
final VncKeyword tkw = (VncKeyword)t;
if (tkw.hasNamespace()) {
return tkw;
}
else if (Types.isCoreType(tkw.getSimpleName())) {
// if it's a core type qualify it with core namespace
return new VncKeyword("core/" + tkw.getSimpleName());
}
else {
return null; // not quiet sure what to do in this case!
}
}
else {
return null;
}
}

private static CallFrame callframe(final VncVal val) {
return new CallFrame("invalid-argument-type", val.getMeta());
}

}
42 changes: 2 additions & 40 deletions src/main/java/com/github/jlangch/venice/impl/FunctionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,8 @@
import com.github.jlangch.venice.impl.types.collections.VncList;
import com.github.jlangch.venice.impl.types.collections.VncSequence;
import com.github.jlangch.venice.impl.types.collections.VncVector;
import com.github.jlangch.venice.impl.types.util.QualifiedName;
import com.github.jlangch.venice.impl.types.util.Types;
import com.github.jlangch.venice.impl.util.ArityExceptions.FnType;
import com.github.jlangch.venice.impl.util.MetaUtil;
import com.github.jlangch.venice.impl.util.callstack.CallFrame;
import com.github.jlangch.venice.impl.util.callstack.CallFrameFnData;
import com.github.jlangch.venice.impl.util.callstack.CallStack;
Expand Down Expand Up @@ -101,7 +99,7 @@ public VncFunction buildFunction(
// Param access optimizations
final VncVal[] paramArr = params.getJavaList().toArray(new VncVal[] {});

final VncKeyword[] paramTypesArr = getParamTypes(paramArr);
final VncKeyword[] paramTypesArr = FunctionArgsTypeHints.getParamTypes(paramArr);

return new VncFunction(name, params, macro, preConditions, meta) {
@Override
Expand Down Expand Up @@ -200,13 +198,7 @@ private void addFnArgsToEnv(final VncList args, final Env env) {
final VncKeyword typeMeta = paramTypesArr[ii];
if (typeMeta != null) {
// check 'val' type against 'typeMeta'
if (!Types.isInstanceOf(typeMeta, val)) {
throw new AssertionException(String.format(
"function argument type not compatible: arg-name=%s, arg-type=%s, expected-type=%s ",
sym.getSimpleName(),
Types.getType(val).toString(true),
typeMeta.toString(true)));
}
FunctionArgsTypeHints.validate(sym, val, typeMeta);
}
env.setLocal(new Var(sym, val, Var.Scope.Local));
}
Expand Down Expand Up @@ -234,36 +226,6 @@ private void validateFnPreconditions(final Env env) {
};
}

private VncKeyword[] getParamTypes(final VncVal[] paramArr) {
final VncKeyword[] types = new VncKeyword[paramArr.length];

for(int ii=0; ii<paramArr.length; ii++) {
final VncVal p = paramArr[ii];
if (Types.isVncSymbol(p)) {
final VncVal t = p.getMetaVal(MetaUtil.TYPE);
if (Types.isVncKeyword(t)) {
final VncKeyword tkw = (VncKeyword)t;
final QualifiedName qn = QualifiedName.parse(tkw.getQualifiedName());
if (qn.isQualified()) {
types[ii] = tkw;
}
else if (Types.isCoreType(qn.getSimpleName())) {
// if it's a core type qualify it with core namespace
types[ii] = new VncKeyword("core/" + qn.getSimpleName());
}
}
else {
types[ii] = null;
}
}
else {
types[ii] = null;
}
}

return types;
}

private void throwVariadicArityException(
final VncFunction fn,
final VncList args,
Expand Down
94 changes: 94 additions & 0 deletions src/test/java/com/github/jlangch/venice/FunctionArgTypeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,98 @@ public void test_fn_call_poly_fail_2() {
assertThrows(AssertionException.class, () -> venice.eval(script));
}


// ------------------------------------------------------------------------
// Multi arity tests
// ------------------------------------------------------------------------

@Test
public void test_fn_call_multiarity_ok_1() {
final Venice venice = new Venice();

final String script1 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f)) ";

assertEquals(0L, venice.eval(script1));

final String script2 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f 1)) ";

assertEquals(1L, venice.eval(script2));

final String script3 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f 1 2)) ";

assertEquals(3L, venice.eval(script3));

final String script4 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f 1 2 3 4 5)) ";

assertEquals(15L, venice.eval(script4));
}

@Test
public void test_fn_call_multiarity_fail_1() {
final Venice venice = new Venice();

final String script2 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f :foo)) ";

assertThrows(AssertionException.class, () -> venice.eval(script2));


final String script3 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f 1 :foo)) ";

assertThrows(AssertionException.class, () -> venice.eval(script3));


final String script4 =
"(do \n" +
" (defn f \n" +
" ([] 0) \n" +
" ([^:long x] x) \n" +
" ([^:long x ^:long y] (+ x y)) \n" +
" ([^:long x ^:long y & xs] (apply + x y xs))) \n" +
" (f 1 :foo 3 4 5)) ";

assertThrows(AssertionException.class, () -> venice.eval(script4));
}

}

0 comments on commit 41dc249

Please sign in to comment.