Skip to content

Commit c1c8201

Browse files
committed
Merge branch 'release/0.41.1'
2 parents 844fcc3 + 74e538d commit c1c8201

File tree

4 files changed

+86
-14
lines changed

4 files changed

+86
-14
lines changed

build/ultratiny.flx

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import libc as _
77

88

99

10-
fn qux(x: str, y: int = 3, args: [str: ...])
10+
fn qux(x: str, y: int = 4, args: [str: ...])
1111
{
1212
printf("x = %s, y = %d\n", x, y)
1313
for a in args
@@ -24,8 +24,8 @@ fn foo(a: int, b: int, c: int, x: int = 9, y: int = 8, z: int = 7)
2424

2525
@entry fn main()
2626
{
27-
foo(x: 4, 1, 2, y: 5, z: 6, 3)
28-
qux(y: 17, "hello, world!", "hi", "my", "name", "is", "bob", "ross")
27+
foo(x: 4, 1, y: 2, 5, z: 6, 3, 2)
28+
qux("hello, world!", 33, "hi", "my", "name", "is", "bob", "ross")
2929
}
3030

3131

source/codegen/call.cpp

+14-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ static std::vector<fir::Value*> _codegenAndArrangeFunctionCallArguments(cgn::Cod
3333

3434
util::hash_map<size_t, sst::Expr*> argExprs;
3535

36+
// this thing below operates similarly to the list solver in typecheck/polymorph/solver.cpp
3637
size_t last_arg = std::min(numNormalArgs, arguments.size());
3738

3839
size_t positionalCounter = 0;
40+
size_t varArgStart = numNormalArgs;
3941
for(size_t i = 0; i < last_arg; i++)
4042
{
4143
const auto& arg = arguments[i];
@@ -52,6 +54,17 @@ static std::vector<fir::Value*> _codegenAndArrangeFunctionCallArguments(cgn::Cod
5254
}
5355
else
5456
{
57+
// so, `positionalCounter` counts the paramters on the declaration-side. thus, once we encounter a default value,
58+
// it must mean that the rest of the parameters will be optional as well.
59+
//* ie. we've passed all the positional arguments already, leaving the optional ones, which means every argument from
60+
//* here onwards (including this one) must be named. since this is *not* named, we just skip straight to the varargs if
61+
//* it was present.
62+
if(fvararg && defaultArgumentValues.find(positionalCounter) != defaultArgumentValues.end())
63+
{
64+
varArgStart = i;
65+
break;
66+
}
67+
5568
argExprs[positionalCounter] = arg.value;
5669
positionalCounter++;
5770
}
@@ -130,8 +143,7 @@ static std::vector<fir::Value*> _codegenAndArrangeFunctionCallArguments(cgn::Cod
130143

131144
// check the variadic arguments. note that IRBuilder will handle actually wrapping the values up into a slice
132145
// and/or creating an empty slice and/or forwarding an existing slice. we just need to supply the values.
133-
// TODO: i'm still a bit suspicious if we are counting things correctly here, wrt. varargs and optional args.
134-
for(size_t i = numNormalArgs; i < arguments.size(); i++)
146+
for(size_t i = varArgStart; i < arguments.size(); i++)
135147
{
136148
auto arg = arguments[i].value;
137149
fir::Type* infer = 0;

source/typecheck/function.cpp

-4
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ TCResult ast::FuncDefn::generateDeclaration(sst::TypecheckState* fs, fir::Type*
4747
p.name, p.type, p.defaultVal->type);
4848
}
4949
}
50-
else if(p.type->isVariadicArrayType())
51-
{
52-
p.defaultVal = util::pool<ast::LitArray>(p.loc, std::vector<Expr*>())->typecheck(fs, p.type).expr();
53-
}
5450

5551
ps.push_back(p);
5652
ptys.push_back(p.type);

source/typecheck/polymorph/solver.cpp

+69-5
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,18 @@ namespace sst
167167
unsolvedtargets.insert(i);
168168
});
169169

170+
// record which optionals we passed, for a better error message.
171+
std::set<std::string> providedOptionals;
170172

171-
size_t last_arg = std::min(target.size() + (fvararg ? -1 : 0), given.size());
172173

174+
size_t last_arg = std::min(target.size() + (fvararg ? -1 : 0), given.size());
173175

174176
// we used to do this check in the parser, but to support more edge cases (like passing varargs)
175177
// we moved it here so we can actually check stuff.
176178
bool didNames = false;
177179

178180
size_t positionalCounter = 0;
181+
size_t varArgStart = last_arg;
179182
for(size_t i = 0; i < last_arg; i++)
180183
{
181184
const ArgType* targ = 0;
@@ -210,24 +213,85 @@ namespace sst
210213
didNames = true;
211214
positionalCounter++;
212215
}
216+
else
217+
{
218+
providedOptionals.insert(given[i].name);
219+
}
213220
}
214221
else
215222
{
223+
/*
224+
we didn't pass a name. if the function is variadic, we might have wanted to pass the following argument(s)
225+
variadically. so, instead of assuming we made a mistake (like not passing the optional by name), assume we
226+
wanted to pass it to the vararg.
227+
228+
so, `positionalCounter` counts the paramters on the declaration-side. thus, once we encounter a default value,
229+
it must mean that the rest of the parameters will be optional as well.
230+
231+
* ie. we've passed all the positional arguments already, leaving the optional ones, which means every argument from
232+
* here onwards (including this one) must be named. since this is *not* named, we just skip straight to the varargs if
233+
* it was present.
234+
*/
235+
216236
targ = &target[positionalCounter];
217-
unsolvedtargets.erase(positionalCounter);
218237

238+
if(fvararg && targ->optional)
239+
{
240+
varArgStart = i;
241+
break;
242+
}
243+
244+
unsolvedtargets.erase(positionalCounter);
219245
positionalCounter++;
220246
}
221247

248+
/*
249+
TODO: not sure if there's a way to get around this, but if we have a function like this:
250+
251+
fn foo(a: int, b: int, c: int, x: int = 9, y: int = 8, z: int = 7) { ... }
252+
253+
then calling it wrongly like this: foo(x: 4, 1, 2, 5, z: 6, 3)
254+
255+
results in an error at the last argument ('3') saying taht optional argument 'x' must be passed by name.
256+
the problem is that we can't really tell what argument you wanted to pass; after seeing '1', '2', and '5',
257+
the positionalCounter now points to the 4th argument, 'x'.
258+
259+
even though you already passed x prior, we don't really know that? and we assume you wanted to pass x (again)
260+
*/
261+
222262
if(given[i].name.empty())
223263
{
224264
if(didNames)
225265
return SimpleError::make(given[i].loc, "positional arguments cannot appear after named arguments");
226266

227267
else if(targ->optional)
228-
return SimpleError::make(given[i].loc, "optional argument '%s' must be passed by name", targ->name);
229-
}
268+
{
269+
std::string probablyIntendedArgumentName;
270+
for(const auto& a : target)
271+
{
272+
if(!a.optional)
273+
continue;
274+
275+
if(auto it = providedOptionals.find(a.name); it == providedOptionals.end())
276+
{
277+
probablyIntendedArgumentName = a.name;
278+
break;
279+
}
280+
};
230281

282+
if(probablyIntendedArgumentName.empty())
283+
{
284+
//* this shouldn't happen, because we only get here if we're not variadic, but if we weren't
285+
//* variadic, then we would've errored out if the argument count was wrong to begin with.
286+
return SimpleError::make(given[i].loc, "extraneous argument without corresponding parameter");
287+
}
288+
else
289+
{
290+
return SimpleError::make(given[i].loc, "optional argument '%s' must be passed by name",
291+
probablyIntendedArgumentName);
292+
}
293+
}
294+
}
231295

232296
iceAssert(targ);
233297
auto err = solveSingleType(soln, targ->toFLT(), given[i].toFLT());
@@ -284,7 +348,7 @@ namespace sst
284348
auto varty = target.back()->toArraySliceType()->getArrayElementType();
285349
auto ltvarty = fir::LocatedType(varty, target.back().loc);
286350

287-
for(size_t i = last_arg; i < given.size(); i++)
351+
for(size_t i = varArgStart; i < given.size(); i++)
288352
{
289353
auto err = solveSingleType(soln, ltvarty, given[i].toFLT());
290354
if(err) return err->append(SimpleError::make(MsgType::Note, target.back().loc, "in argument of variadic parameter"));

0 commit comments

Comments
 (0)